const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; const expect = std.testing.expect; test "simple switch loop" { 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 S = struct { fn doTheTest() !void { var start: u32 = undefined; start = 32; const result: u32 = s: switch (start) { 0 => 0, 1 => 1, 2 => 2, 3 => 3, else => |x| continue :s x / 2, }; try expect(result == 2); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop with ranges" { 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 S = struct { fn doTheTest() !void { var start: u32 = undefined; start = 32; const result = s: switch (start) { 0...3 => |x| x, else => |x| continue :s x / 2, }; try expect(result == 2); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop on enum" { 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 S = struct { const E = enum { a, b, c }; fn doTheTest() !void { var start: E = undefined; start = .a; const result: u32 = s: switch (start) { .a => continue :s .b, .b => continue :s .c, .c => 123, }; try expect(result == 123); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop with error set" { 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 S = struct { const E = error{ Foo, Bar, Baz }; fn doTheTest() !void { var start: E = undefined; start = error.Foo; const result: u32 = s: switch (start) { error.Foo => continue :s error.Bar, error.Bar => continue :s error.Baz, error.Baz => 123, }; try expect(result == 123); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop on tagged union" { 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; const S = struct { const U = union(enum) { a: u32, b: f32, c: f32, }; fn doTheTest() !void { var start: U = undefined; start = .{ .a = 80 }; const result = s: switch (start) { .a => |x| switch (x) { 0...49 => continue :s .{ .b = @floatFromInt(x) }, 50 => continue :s .{ .c = @floatFromInt(x) }, else => continue :s .{ .a = x / 2 }, }, .b => |x| x, .c => return error.TestFailed, }; try expect(result == 40.0); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop dispatching instructions" { 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 S = struct { const Inst = union(enum) { set: u32, add: u32, sub: u32, end, }; fn doTheTest() !void { var insts: [5]Inst = undefined; @memcpy(&insts, &[5]Inst{ .{ .set = 123 }, .{ .add = 100 }, .{ .sub = 50 }, .{ .sub = 10 }, .end, }); var i: u32 = 0; var cur: u32 = undefined; eval: switch (insts[0]) { .set => |x| { cur = x; i += 1; continue :eval insts[i]; }, .add => |x| { cur += x; i += 1; continue :eval insts[i]; }, .sub => |x| { cur -= x; i += 1; continue :eval insts[i]; }, .end => {}, } try expect(cur == 163); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop with pointer capture" { 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 S = struct { const U = union(enum) { a: u32, b: u32, c: u32, }; fn doTheTest() !void { var a: U = .{ .a = 100 }; var b: U = .{ .b = 200 }; var c: U = .{ .c = 300 }; inc: switch (a) { .a => |*x| { x.* += 1; continue :inc b; }, .b => |*x| { x.* += 10; continue :inc c; }, .c => |*x| { x.* += 50; }, } try expect(a.a == 101); try expect(b.b == 210); try expect(c.c == 350); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "unanalyzed continue with operand" { @setRuntimeSafety(false); label: switch (false) { false => if (false) continue :label true, true => {}, } } test "switch loop on larger than pointer integer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var entry: @Int(.unsigned, @bitSizeOf(usize) + 1) = undefined; entry = 0; loop: switch (entry) { 0 => { entry += 1; continue :loop 1; }, 1 => |x| { entry += 1; continue :loop x + 1; }, 2 => entry += 1, else => unreachable, } try expect(entry == 3); } test "switch loop on non-exhaustive enum" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO 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 S = struct { const E = enum(u8) { a, b, c, _ }; fn doTheTest() !void { var start: E = undefined; start = .a; const result: u32 = s: switch (start) { .a => continue :s .c, else => continue :s @enumFromInt(123), .b, _ => |x| break :s @intFromEnum(x), }; try expect(result == 123); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop with discarded tag capture" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const S = struct { const U = union(enum) { a: u32, b: u32, c: u32, }; fn doTheTest() void { const a: U = .{ .a = 10 }; blk: switch (a) { inline .b => |_, tag| { _ = tag; continue :blk .{ .c = 20 }; }, else => {}, } } }; S.doTheTest(); comptime S.doTheTest(); } test "switch loop with single catch-all prong" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const S = struct { const E = enum { a, b, c }; const U = union(E) { a: u32, b: u16, c: u8 }; fn doTheTest() !void { var x: usize = 0; label: switch (E.a) { else => { x += 1; if (x == 10) break :label; if (x >= 5) continue :label .b; continue :label .c; }, } try expect(x == 10); label: switch (E.a) { .a, .b, .c => { x += 1; if (x == 20) break :label; if (x >= 15) continue :label .b; continue :label .c; }, } try expect(x == 20); label: switch (E.a) { else => if (false) continue :label true, } const ok = label: switch (U{ .a = 123 }) { else => |u| { const y: u32 = switch (u) { inline else => |y| y, }; if (y == 456) break :label true; continue :label .{ .b = 456 }; }, }; comptime assert(ok); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop on type with opv" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const S = struct { const E = enum { opv }; const U = union(E) { opv: u0 }; fn doTheTest() !void { var x: usize = 0; label: switch (E.opv) { .opv => { x += 1; if (x == 10) break :label; if (x >= 5) continue :label .opv; continue :label .opv; }, } try expect(x == 10); label: switch (E.opv) { else => { x += 1; if (x == 20) break :label; if (x >= 15) continue :label .opv; continue :label .opv; }, } try expect(x == 20); label: switch (E.opv) { .opv => if (false) continue :label true, } label: switch (U{ .opv = 0 }) { .opv => |val| { x += 1; if (x == 30) break :label; if (x >= 25) continue :label .{ .opv = val }; continue :label .{ .opv = 0 }; }, } try expect(x == 30); } }; try S.doTheTest(); try comptime S.doTheTest(); } test "switch loop 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 { const ok1 = label: switch (u) { .a => |nothing, tag| { comptime assert(nothing == {}); comptime assert(tag == .a); try expect(@intFromEnum(tag) == @intFromEnum(@This().a)); continue :label .{ .d = 456 }; }, .b, .d => |_, tag| { try expect(tag == .b or tag == .d); continue :label .{ .c = 0x0F }; }, .e => |payload, tag| { _ = &payload; _ = &tag; return error.AnalyzedNoreturnProng; }, else => |un, tag| { try expect(tag == .c); try expect(un == .c); if (un.c == 0xFF) continue :label .a; if (un.c == 0x00) break :label false; break :label true; }, }; try expect(ok1); const ok2 = label: switch (u) { inline .a, .b, .c => |payload, tag| { if (@TypeOf(payload) == void) { comptime assert(tag == .a); continue :label .{ .b = 456 }; } if (@TypeOf(payload) == i32) { comptime assert(tag == .b); continue :label .{ .d = payload }; } if (@TypeOf(payload) == u8) { comptime assert(tag == .c); continue :label .{ .d = payload }; } }, inline else => |payload, tag| { if (@TypeOf(payload) == i32) comptime assert(tag == .d); comptime assert(tag != .e); if (payload == 0) break :label false; break :label true; }, }; try expect(ok2); } }; try U.doTheTest(); try comptime U.doTheTest(); } test "switch loop for error handling" { const Error = error{ MyError, MyOtherError }; const S = struct { fn doTheTest() !void { try doThePayloadSwitch(123); try doTheErrSwitch(error.MyError); try doTheErrSwitch(error.MyOtherError); } fn doThePayloadSwitch(eu: Error!u32) !void { const x = eu catch |err| label: switch (err) { error.MyError => continue :label error.MyOtherError, error.MyOtherError => break :label 0, }; try expect(x == 123); const y = if (eu) |payload| label: { break :label payload * 2; } else |err| label: switch (err) { error.MyError => continue :label error.MyOtherError, error.MyOtherError => break :label 0, }; try expect(y == 246); } fn doTheErrSwitch(eu: Error!u32) !void { const x = eu catch |err| label: switch (err) { error.MyError => continue :label error.MyOtherError, error.MyOtherError => break :label 123, }; try expect(x == 123); const y = if (eu) |payload| label: { break :label payload * 2; } else |err| label: switch (err) { error.MyError => continue :label error.MyOtherError, error.MyOtherError => break :label 123, }; try expect(y == 123); } }; try S.doTheTest(); try comptime S.doTheTest(); }