AstGen: fix while and for with unreachable bodies

Companion commit to 61a53a587558ff1fe1b0ec98bb424022885edccf.

This commit also moves over a bunch of behavior test cases to the
passing-for-stage2 section.
This commit is contained in:
Andrew Kelley 2021-10-02 20:15:03 -07:00
parent 61a53a5875
commit c4df9bf56f
22 changed files with 2309 additions and 1679 deletions

View File

@ -5537,8 +5537,10 @@ fn whileExpr(
});
}
loop_scope.break_count += 1;
const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
if (!then_scope.endsWithNoReturn()) {
loop_scope.break_count += 1;
}
try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
var else_scope = parent_gz.makeSubBlock(&continue_scope.base);
@ -5549,7 +5551,6 @@ fn whileExpr(
src: Ast.Node.Index,
result: Zir.Inst.Ref,
} = if (else_node != 0) blk: {
loop_scope.break_count += 1;
const sub_scope = s: {
if (while_full.error_token) |error_token| {
const tag: Zir.Inst.Tag = if (payload_is_ref)
@ -5576,6 +5577,9 @@ fn whileExpr(
}
};
const e = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
if (!else_scope.endsWithNoReturn()) {
loop_scope.break_count += 1;
}
try checkUsed(parent_gz, &else_scope.base, sub_scope);
break :blk .{
.src = else_node,
@ -5746,8 +5750,10 @@ fn forExpr(
break :blk &index_scope.base;
};
loop_scope.break_count += 1;
const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
if (!then_scope.endsWithNoReturn()) {
loop_scope.break_count += 1;
}
try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
var else_scope = parent_gz.makeSubBlock(&cond_scope.base);
@ -5758,11 +5764,14 @@ fn forExpr(
src: Ast.Node.Index,
result: Zir.Inst.Ref,
} = if (else_node != 0) blk: {
loop_scope.break_count += 1;
const sub_scope = &else_scope.base;
const else_result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node);
if (!else_scope.endsWithNoReturn()) {
loop_scope.break_count += 1;
}
break :blk .{
.src = else_node,
.result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node),
.result = else_result,
};
} else .{
.src = for_full.ast.then_expr,

View File

@ -1605,7 +1605,15 @@ pub const FuncGen = struct {
self.builder.positionBuilderAtEnd(loop_block);
try self.genBody(body);
_ = self.builder.buildBr(loop_block);
// TODO instead of this logic, change AIR to have the property that
// every block is guaranteed to end with a noreturn instruction.
// Then we can simply rely on the fact that a repeat or break instruction
// would have been emitted already. Also the main loop in genBody can
// be while(true) instead of for(body), which will eliminate 1 branch on
// a hot path.
if (body.len == 0 or !self.air.typeOfIndex(body[body.len - 1]).isNoReturn()) {
_ = self.builder.buildBr(loop_block);
}
return null;
}

View File

@ -15,8 +15,12 @@ test {
_ = @import("behavior/bugs/4769_b.zig");
_ = @import("behavior/bugs/6850.zig");
_ = @import("behavior/bugs/9584.zig");
_ = @import("behavior/call.zig");
_ = @import("behavior/cast.zig");
_ = @import("behavior/defer.zig");
_ = @import("behavior/enum.zig");
_ = @import("behavior/eval.zig");
_ = @import("behavior/for.zig");
_ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
_ = @import("behavior/math.zig");
@ -24,11 +28,14 @@ test {
_ = @import("behavior/pointers.zig");
_ = @import("behavior/sizeof_and_typeof.zig");
_ = @import("behavior/struct.zig");
_ = @import("behavior/switch.zig");
_ = @import("behavior/this.zig");
_ = @import("behavior/translate_c_macros.zig");
_ = @import("behavior/underscore.zig");
_ = @import("behavior/union.zig");
_ = @import("behavior/usingnamespace.zig");
_ = @import("behavior/widening.zig");
_ = @import("behavior/while.zig");
if (builtin.zig_is_stage2) {
// When all comptime_memory.zig tests pass, #9646 can be closed.
@ -98,12 +105,11 @@ test {
_ = @import("behavior/bugs/7250.zig");
_ = @import("behavior/byteswap.zig");
_ = @import("behavior/byval_arg_var.zig");
_ = @import("behavior/call.zig");
_ = @import("behavior/call_stage1.zig");
_ = @import("behavior/cast_stage1.zig");
_ = @import("behavior/const_slice_child.zig");
_ = @import("behavior/defer.zig");
_ = @import("behavior/enum.zig");
_ = @import("behavior/enum_with_members.zig");
_ = @import("behavior/defer_stage1.zig");
_ = @import("behavior/enum_stage1.zig");
_ = @import("behavior/error.zig");
_ = @import("behavior/eval_stage1.zig");
_ = @import("behavior/field_parent_ptr.zig");
@ -111,7 +117,7 @@ test {
_ = @import("behavior/fn.zig");
_ = @import("behavior/fn_in_struct_in_comptime.zig");
_ = @import("behavior/fn_delegation.zig");
_ = @import("behavior/for.zig");
_ = @import("behavior/for_stage1.zig");
_ = @import("behavior/generics_stage1.zig");
_ = @import("behavior/hasdecl.zig");
_ = @import("behavior/hasfield.zig");
@ -143,7 +149,7 @@ test {
_ = @import("behavior/struct_stage1.zig");
_ = @import("behavior/struct_contains_null_ptr_itself.zig");
_ = @import("behavior/struct_contains_slice_of_itself.zig");
_ = @import("behavior/switch.zig");
_ = @import("behavior/switch_stage1.zig");
_ = @import("behavior/switch_prong_err_enum.zig");
_ = @import("behavior/switch_prong_implicit_cast.zig");
_ = @import("behavior/truncate.zig");
@ -153,8 +159,8 @@ test {
_ = @import("behavior/type_info.zig");
_ = @import("behavior/typename.zig");
_ = @import("behavior/undefined.zig");
_ = @import("behavior/underscore.zig");
_ = @import("behavior/union_stage1.zig");
_ = @import("behavior/union_with_members.zig");
_ = @import("behavior/usingnamespace_stage1.zig");
_ = @import("behavior/var_args.zig");
_ = @import("behavior/vector.zig");
@ -162,7 +168,7 @@ test {
if (builtin.target.cpu.arch == .wasm32) {
_ = @import("behavior/wasm.zig");
}
_ = @import("behavior/while.zig");
_ = @import("behavior/while_stage1.zig");
_ = @import("behavior/src.zig");
_ = @import("behavior/translate_c_macros_stage1.zig");
}

View File

@ -1,74 +1,3 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
test "basic invocations" {
const foo = struct {
fn foo() i32 {
return 1234;
}
}.foo;
try expect(@call(.{}, foo, .{}) == 1234);
comptime {
// modifiers that allow comptime calls
try expect(@call(.{}, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .no_async }, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .always_tail }, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234);
}
{
// comptime call without comptime keyword
const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234;
comptime try expect(result);
}
{
// call of non comptime-known function
var alias_foo = foo;
try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234);
try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234);
try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234);
}
}
test "tuple parameters" {
const add = struct {
fn add(a: i32, b: i32) i32 {
return a + b;
}
}.add;
var a: i32 = 12;
var b: i32 = 34;
try expect(@call(.{}, add, .{ a, 34 }) == 46);
try expect(@call(.{}, add, .{ 12, b }) == 46);
try expect(@call(.{}, add, .{ a, b }) == 46);
try expect(@call(.{}, add, .{ 12, 34 }) == 46);
comptime try expect(@call(.{}, add, .{ 12, 34 }) == 46);
{
const separate_args0 = .{ a, b };
const separate_args1 = .{ a, 34 };
const separate_args2 = .{ 12, 34 };
const separate_args3 = .{ 12, b };
try expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46);
}
}
test "comptime call with bound function as parameter" {
const S = struct {
fn ReturnType(func: anytype) type {
return switch (@typeInfo(@TypeOf(func))) {
.BoundFn => |info| info,
else => unreachable,
}.return_type orelse void;
}
fn call_me_maybe() ?i32 {
return 123;
}
};
var inst: S = undefined;
try expectEqual(?i32, S.ReturnType(inst.call_me_maybe));
}

View File

@ -0,0 +1,74 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
test "basic invocations" {
const foo = struct {
fn foo() i32 {
return 1234;
}
}.foo;
try expect(@call(.{}, foo, .{}) == 1234);
comptime {
// modifiers that allow comptime calls
try expect(@call(.{}, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .no_async }, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .always_tail }, foo, .{}) == 1234);
try expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234);
}
{
// comptime call without comptime keyword
const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234;
comptime try expect(result);
}
{
// call of non comptime-known function
var alias_foo = foo;
try expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234);
try expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234);
try expect(@call(.{ .modifier = .never_inline }, alias_foo, .{}) == 1234);
}
}
test "tuple parameters" {
const add = struct {
fn add(a: i32, b: i32) i32 {
return a + b;
}
}.add;
var a: i32 = 12;
var b: i32 = 34;
try expect(@call(.{}, add, .{ a, 34 }) == 46);
try expect(@call(.{}, add, .{ 12, b }) == 46);
try expect(@call(.{}, add, .{ a, b }) == 46);
try expect(@call(.{}, add, .{ 12, 34 }) == 46);
comptime try expect(@call(.{}, add, .{ 12, 34 }) == 46);
{
const separate_args0 = .{ a, b };
const separate_args1 = .{ a, 34 };
const separate_args2 = .{ 12, 34 };
const separate_args3 = .{ 12, b };
try expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46);
try expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46);
}
}
test "comptime call with bound function as parameter" {
const S = struct {
fn ReturnType(func: anytype) type {
return switch (@typeInfo(@TypeOf(func))) {
.BoundFn => |info| info,
else => unreachable,
}.return_type orelse void;
}
fn call_me_maybe() ?i32 {
return 123;
}
};
var inst: S = undefined;
try expectEqual(?i32, S.ReturnType(inst.call_me_maybe));
}

View File

@ -16,3 +16,52 @@ test "integer literal to pointer cast" {
const vga_mem = @intToPtr(*u16, 0xB8000);
try expect(@ptrToInt(vga_mem) == 0xB8000);
}
test "peer type resolution: ?T and T" {
try expect(peerTypeTAndOptionalT(true, false).? == 0);
try expect(peerTypeTAndOptionalT(false, false).? == 3);
comptime {
try expect(peerTypeTAndOptionalT(true, false).? == 0);
try expect(peerTypeTAndOptionalT(false, false).? == 3);
}
}
fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
if (c) {
return if (b) null else @as(usize, 0);
}
return @as(usize, 3);
}
test "resolve undefined with integer" {
try testResolveUndefWithInt(true, 1234);
comptime try testResolveUndefWithInt(true, 1234);
}
fn testResolveUndefWithInt(b: bool, x: i32) !void {
const value = if (b) x else undefined;
if (b) {
try expect(value == x);
}
}
test "@intCast i32 to u7" {
var x: u128 = maxInt(u128);
var y: i32 = 120;
var z = x >> @intCast(u7, y);
try expect(z == 0xff);
}
test "@intCast to comptime_int" {
try expect(@intCast(comptime_int, 0) == 0);
}
test "implicit cast comptime numbers to any type when the value fits" {
const a: u64 = 255;
var b: u8 = a;
try expect(b == 255);
}
test "implicit cast comptime_int to comptime_float" {
comptime try expect(@as(comptime_float, 10) == @as(f32, 10));
try expect(2 == 2.0);
}

View File

@ -121,22 +121,6 @@ fn returnNullLitFromOptionalTypeErrorRef() anyerror!?*A {
return null;
}
test "peer type resolution: ?T and T" {
try expect(peerTypeTAndOptionalT(true, false).? == 0);
try expect(peerTypeTAndOptionalT(false, false).? == 3);
comptime {
try expect(peerTypeTAndOptionalT(true, false).? == 0);
try expect(peerTypeTAndOptionalT(false, false).? == 3);
}
}
fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
if (c) {
return if (b) null else @as(usize, 0);
}
return @as(usize, 3);
}
test "peer type resolution: [0]u8 and []const u8" {
try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
@ -203,17 +187,6 @@ fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 {
return slice[0..1];
}
test "resolve undefined with integer" {
try testResolveUndefWithInt(true, 1234);
comptime try testResolveUndefWithInt(true, 1234);
}
fn testResolveUndefWithInt(b: bool, x: i32) !void {
const value = if (b) x else undefined;
if (b) {
try expect(value == x);
}
}
test "implicit cast from &const [N]T to []const T" {
try testCastConstArrayRefToConstSlice();
comptime try testCastConstArrayRefToConstSlice();
@ -424,13 +397,6 @@ test "comptime_int @intToFloat" {
}
}
test "@intCast i32 to u7" {
var x: u128 = maxInt(u128);
var y: i32 = 120;
var z = x >> @intCast(u7, y);
try expect(z == 0xff);
}
test "@floatCast cast down" {
{
var double: f64 = 0.001534;
@ -533,20 +499,8 @@ test "implicit ptr to *c_void" {
try expect(c.* == 1);
}
test "@intCast to comptime_int" {
try expect(@intCast(comptime_int, 0) == 0);
}
test "implicit cast comptime numbers to any type when the value fits" {
const a: u64 = 255;
var b: u8 = a;
try expect(b == 255);
}
test "@intToEnum passed a comptime_int to an enum with one item" {
const E = enum {
A,
};
const E = enum { A };
const x = @intToEnum(E, 0);
try expect(x == E.A);
}
@ -599,11 +553,6 @@ test "peer type resolution: unreachable, error set, unreachable" {
try expect(transformed_err == error.SystemResources);
}
test "implicit cast comptime_int to comptime_float" {
comptime try expect(@as(comptime_float, 10) == @as(f32, 10));
try expect(2 == 2.0);
}
test "implicit cast *[0]T to E![]const u8" {
var x = @as(anyerror![]const u8, &[0]u8{});
try expect((x catch unreachable).len == 0);
@ -645,12 +594,7 @@ test "*const [N]null u8 to ?[]const u8" {
test "peer resolution of string literals" {
const S = struct {
const E = enum {
a,
b,
c,
d,
};
const E = enum { a, b, c, d };
fn doTheTest(e: E) !void {
const cmd = switch (e) {

View File

@ -6,11 +6,7 @@ const expect = testing.expect;
var argv: [*]const [*]const u8 = undefined;
test "const slice child" {
const strs = [_][*]const u8{
"one",
"two",
"three",
};
const strs = [_][*]const u8{ "one", "two", "three" };
argv = &strs;
try bar(strs.len);
}

View File

@ -2,113 +2,3 @@ const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
var result: [3]u8 = undefined;
var index: usize = undefined;
fn runSomeErrorDefers(x: bool) !bool {
index = 0;
defer {
result[index] = 'a';
index += 1;
}
errdefer {
result[index] = 'b';
index += 1;
}
defer {
result[index] = 'c';
index += 1;
}
return if (x) x else error.FalseNotAllowed;
}
test "mixing normal and error defers" {
try expect(runSomeErrorDefers(true) catch unreachable);
try expect(result[0] == 'c');
try expect(result[1] == 'a');
const ok = runSomeErrorDefers(false) catch |err| x: {
try expect(err == error.FalseNotAllowed);
break :x true;
};
try expect(ok);
try expect(result[0] == 'c');
try expect(result[1] == 'b');
try expect(result[2] == 'a');
}
test "break and continue inside loop inside defer expression" {
testBreakContInDefer(10);
comptime testBreakContInDefer(10);
}
fn testBreakContInDefer(x: usize) void {
defer {
var i: usize = 0;
while (i < x) : (i += 1) {
if (i < 5) continue;
if (i == 5) break;
}
expect(i == 5) catch @panic("test failure");
}
}
test "defer and labeled break" {
var i = @as(usize, 0);
blk: {
defer i += 1;
break :blk;
}
try expect(i == 1);
}
test "errdefer does not apply to fn inside fn" {
if (testNestedFnErrDefer()) |_| @panic("expected error") else |e| try expect(e == error.Bad);
}
fn testNestedFnErrDefer() anyerror!void {
var a: i32 = 0;
errdefer a += 1;
const S = struct {
fn baz() anyerror {
return error.Bad;
}
};
return S.baz();
}
test "return variable while defer expression in scope to modify it" {
const S = struct {
fn doTheTest() !void {
try expect(notNull().? == 1);
}
fn notNull() ?u8 {
var res: ?u8 = 1;
defer res = null;
return res;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "errdefer with payload" {
const S = struct {
fn foo() !i32 {
errdefer |a| {
expectEqual(error.One, a) catch @panic("test failure");
}
return error.One;
}
fn doTheTest() !void {
try expectError(error.One, foo());
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -0,0 +1,114 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
var result: [3]u8 = undefined;
var index: usize = undefined;
fn runSomeErrorDefers(x: bool) !bool {
index = 0;
defer {
result[index] = 'a';
index += 1;
}
errdefer {
result[index] = 'b';
index += 1;
}
defer {
result[index] = 'c';
index += 1;
}
return if (x) x else error.FalseNotAllowed;
}
test "mixing normal and error defers" {
try expect(runSomeErrorDefers(true) catch unreachable);
try expect(result[0] == 'c');
try expect(result[1] == 'a');
const ok = runSomeErrorDefers(false) catch |err| x: {
try expect(err == error.FalseNotAllowed);
break :x true;
};
try expect(ok);
try expect(result[0] == 'c');
try expect(result[1] == 'b');
try expect(result[2] == 'a');
}
test "break and continue inside loop inside defer expression" {
testBreakContInDefer(10);
comptime testBreakContInDefer(10);
}
fn testBreakContInDefer(x: usize) void {
defer {
var i: usize = 0;
while (i < x) : (i += 1) {
if (i < 5) continue;
if (i == 5) break;
}
expect(i == 5) catch @panic("test failure");
}
}
test "defer and labeled break" {
var i = @as(usize, 0);
blk: {
defer i += 1;
break :blk;
}
try expect(i == 1);
}
test "errdefer does not apply to fn inside fn" {
if (testNestedFnErrDefer()) |_| @panic("expected error") else |e| try expect(e == error.Bad);
}
fn testNestedFnErrDefer() anyerror!void {
var a: i32 = 0;
errdefer a += 1;
const S = struct {
fn baz() anyerror {
return error.Bad;
}
};
return S.baz();
}
test "return variable while defer expression in scope to modify it" {
const S = struct {
fn doTheTest() !void {
try expect(notNull().? == 1);
}
fn notNull() ?u8 {
var res: ?u8 = 1;
defer res = null;
return res;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "errdefer with payload" {
const S = struct {
fn foo() !i32 {
errdefer |a| {
expectEqual(error.One, a) catch @panic("test failure");
}
return error.One;
}
fn doTheTest() !void {
try expectError(error.One, foo());
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -1,133 +1,7 @@
const expect = @import("std").testing.expect;
const mem = @import("std").mem;
const Tag = @import("std").meta.Tag;
test "non-exhaustive enum" {
const S = struct {
const E = enum(u8) {
a,
b,
_,
};
fn doTheTest(y: u8) !void {
var e: E = .b;
try expect(switch (e) {
.a => false,
.b => true,
_ => false,
});
e = @intToEnum(E, 12);
try expect(switch (e) {
.a => false,
.b => false,
_ => true,
});
try expect(switch (e) {
.a => false,
.b => false,
else => true,
});
e = .b;
try expect(switch (e) {
.a => false,
else => true,
});
try expect(@typeInfo(E).Enum.fields.len == 2);
e = @intToEnum(E, 12);
try expect(@enumToInt(e) == 12);
e = @intToEnum(E, y);
try expect(@enumToInt(e) == 52);
try expect(@typeInfo(E).Enum.is_exhaustive == false);
}
};
try S.doTheTest(52);
comptime try S.doTheTest(52);
}
test "empty non-exhaustive enum" {
const S = struct {
const E = enum(u8) {
_,
};
fn doTheTest(y: u8) !void {
var e = @intToEnum(E, y);
try expect(switch (e) {
_ => true,
});
try expect(@enumToInt(e) == y);
try expect(@typeInfo(E).Enum.fields.len == 0);
try expect(@typeInfo(E).Enum.is_exhaustive == false);
}
};
try S.doTheTest(42);
comptime try S.doTheTest(42);
}
test "single field non-exhaustive enum" {
const S = struct {
const E = enum(u8) {
a,
_,
};
fn doTheTest(y: u8) !void {
var e: E = .a;
try expect(switch (e) {
.a => true,
_ => false,
});
e = @intToEnum(E, 12);
try expect(switch (e) {
.a => false,
_ => true,
});
try expect(switch (e) {
.a => false,
else => true,
});
e = .a;
try expect(switch (e) {
.a => true,
else => false,
});
try expect(@enumToInt(@intToEnum(E, y)) == y);
try expect(@typeInfo(E).Enum.fields.len == 1);
try expect(@typeInfo(E).Enum.is_exhaustive == false);
}
};
try S.doTheTest(23);
comptime try S.doTheTest(23);
}
test "enum type" {
const foo1 = Foo{ .One = 13 };
const foo2 = Foo{
.Two = Point{
.x = 1234,
.y = 5678,
},
};
try expect(foo1.One == 13);
try expect(foo2.Two.x == 1234 and foo2.Two.y == 5678);
const bar = Bar.B;
try expect(bar == Bar.B);
try expect(@typeInfo(Foo).Union.fields.len == 3);
try expect(@typeInfo(Bar).Enum.fields.len == 4);
try expect(@sizeOf(Foo) == @sizeOf(FooNoVoid));
try expect(@sizeOf(Bar) == 1);
}
test "enum as return value" {
switch (returnAnInt(13)) {
Foo.One => |value| try expect(value == 13),
else => unreachable,
}
}
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
const Tag = std.meta.Tag;
const Point = struct {
x: u64,
@ -153,13 +27,6 @@ fn returnAnInt(x: i32) Foo {
return Foo{ .One = x };
}
test "constant enum with payload" {
var empty = AnEnumWithPayload{ .Empty = {} };
var full = AnEnumWithPayload{ .Full = 13 };
shouldBeEmpty(empty);
shouldBeNotEmpty(full);
}
fn shouldBeEmpty(x: AnEnumWithPayload) void {
switch (x) {
AnEnumWithPayload.Empty => {},
@ -179,72 +46,19 @@ const AnEnumWithPayload = union(enum) {
Full: i32,
};
const Number = enum {
Zero,
One,
Two,
Three,
Four,
};
test "enum to int" {
try shouldEqual(Number.Zero, 0);
try shouldEqual(Number.One, 1);
try shouldEqual(Number.Two, 2);
try shouldEqual(Number.Three, 3);
try shouldEqual(Number.Four, 4);
}
const Number = enum { Zero, One, Two, Three, Four };
fn shouldEqual(n: Number, expected: u3) !void {
try expect(@enumToInt(n) == expected);
}
test "int to enum" {
try testIntToEnumEval(3);
}
fn testIntToEnumEval(x: i32) !void {
try expect(@intToEnum(IntToEnumNumber, x) == IntToEnumNumber.Three);
}
const IntToEnumNumber = enum {
Zero,
One,
Two,
Three,
Four,
};
test "@tagName" {
try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
comptime try expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three"));
}
test "@tagName non-exhaustive enum" {
try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
comptime try expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B"));
}
fn testEnumTagNameBare(n: anytype) []const u8 {
return @tagName(n);
}
const BareNumber = enum {
One,
Two,
Three,
};
const BareNumber = enum { One, Two, Three };
const NonExhaustive = enum(u8) {
A,
B,
_,
};
test "enum alignment" {
comptime {
try expect(@alignOf(AlignTestEnum) >= @alignOf([9]u8));
try expect(@alignOf(AlignTestEnum) >= @alignOf(u64));
}
}
const NonExhaustive = enum(u8) { A, B, _ };
const AlignTestEnum = union(enum) {
A: [9]u8,
@ -776,67 +590,12 @@ const ValueCount257 = enum {
I256,
};
test "enum sizes" {
comptime {
try expect(@sizeOf(ValueCount1) == 0);
try expect(@sizeOf(ValueCount2) == 1);
try expect(@sizeOf(ValueCount256) == 1);
try expect(@sizeOf(ValueCount257) == 2);
}
}
const Small2 = enum(u2) { One, Two };
const Small = enum(u2) { One, Two, Three, Four };
const Small2 = enum(u2) {
One,
Two,
};
const Small = enum(u2) {
One,
Two,
Three,
Four,
};
test "set enum tag type" {
{
var x = Small.One;
x = Small.Two;
comptime try expect(Tag(Small) == u2);
}
{
var x = Small2.One;
x = Small2.Two;
comptime try expect(Tag(Small2) == u2);
}
}
const A = enum(u3) {
One,
Two,
Three,
Four,
One2,
Two2,
Three2,
Four2,
};
const B = enum(u3) {
One3,
Two3,
Three3,
Four3,
One23,
Two23,
Three23,
Four23,
};
const C = enum(u2) {
One4,
Two4,
Three4,
Four4,
};
const A = enum(u3) { One, Two, Three, Four, One2, Two2, Three2, Four2 };
const B = enum(u3) { One3, Two3, Three3, Four3, One23, Two23, Three23, Four23 };
const C = enum(u2) { One4, Two4, Three4, Four4 };
const BitFieldOfEnums = packed struct {
a: A,
@ -850,21 +609,6 @@ const bit_field_1 = BitFieldOfEnums{
.c = C.Four4,
};
test "bit field access with enum fields" {
var data = bit_field_1;
try expect(getA(&data) == A.Two);
try expect(getB(&data) == B.Three3);
try expect(getC(&data) == C.Four4);
comptime try expect(@sizeOf(BitFieldOfEnums) == 1);
data.b = B.Four3;
try expect(data.b == B.Four3);
data.a = A.Three;
try expect(data.a == A.Three);
try expect(data.b == B.Four3);
}
fn getA(data: *const BitFieldOfEnums) A {
return data.a;
}
@ -877,15 +621,6 @@ fn getC(data: *const BitFieldOfEnums) C {
return data.c;
}
test "casting enum to its tag type" {
try testCastEnumTag(Small2.Two);
comptime try testCastEnumTag(Small2.Two);
}
fn testCastEnumTag(value: Small2) !void {
try expect(@enumToInt(value) == 1);
}
const MultipleChoice = enum(u32) {
A = 20,
B = 40,
@ -893,21 +628,6 @@ const MultipleChoice = enum(u32) {
D = 1000,
};
test "enum with specified tag values" {
try testEnumWithSpecifiedTagValues(MultipleChoice.C);
comptime try testEnumWithSpecifiedTagValues(MultipleChoice.C);
}
fn testEnumWithSpecifiedTagValues(x: MultipleChoice) !void {
try expect(@enumToInt(x) == 60);
try expect(1234 == switch (x) {
MultipleChoice.A => 1,
MultipleChoice.B => 2,
MultipleChoice.C => @as(u32, 1234),
MultipleChoice.D => 4,
});
}
const MultipleChoice2 = enum(u32) {
Unspecified1,
A = 20,
@ -920,34 +640,7 @@ const MultipleChoice2 = enum(u32) {
Unspecified5,
};
test "enum with specified and unspecified tag values" {
try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D);
}
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void {
try expect(@enumToInt(x) == 1000);
try expect(1234 == switch (x) {
MultipleChoice2.A => 1,
MultipleChoice2.B => 2,
MultipleChoice2.C => 3,
MultipleChoice2.D => @as(u32, 1234),
MultipleChoice2.Unspecified1 => 5,
MultipleChoice2.Unspecified2 => 6,
MultipleChoice2.Unspecified3 => 7,
MultipleChoice2.Unspecified4 => 8,
MultipleChoice2.Unspecified5 => 9,
});
}
test "cast integer literal to enum" {
try expect(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1);
try expect(@intToEnum(MultipleChoice2, 40) == MultipleChoice2.B);
}
const EnumWithOneMember = enum {
Eof,
};
const EnumWithOneMember = enum { Eof };
fn doALoopThing(id: EnumWithOneMember) void {
while (true) {
@ -958,20 +651,7 @@ fn doALoopThing(id: EnumWithOneMember) void {
}
}
test "comparison operator on enum with one member is comptime known" {
doALoopThing(EnumWithOneMember.Eof);
}
const State = enum {
Start,
};
test "switch on enum with one member is comptime known" {
var state = State.Start;
switch (state) {
State.Start => return,
}
@compileError("analysis should not reach here");
}
const State = enum { Start };
const EnumWithTagValues = enum(u4) {
A = 1 << 0,
@ -979,24 +659,22 @@ const EnumWithTagValues = enum(u4) {
C = 1 << 2,
D = 1 << 3,
};
test "enum with tag values don't require parens" {
try expect(@enumToInt(EnumWithTagValues.C) == 0b0100);
test "enum to int" {
try shouldEqual(Number.Zero, 0);
try shouldEqual(Number.One, 1);
try shouldEqual(Number.Two, 2);
try shouldEqual(Number.Three, 3);
try shouldEqual(Number.Four, 4);
}
test "enum with 1 field but explicit tag type should still have the tag type" {
const Enum = enum(u8) {
B = 2,
};
comptime try expect(@sizeOf(Enum) == @sizeOf(u8));
}
test "tag name with assigned enum values" {
const LocalFoo = enum(u8) {
A = 1,
B = 0,
};
var b = LocalFoo.B;
try expect(mem.eql(u8, @tagName(b), "B"));
test "enum sizes" {
comptime {
try expect(@sizeOf(ValueCount1) == 0);
try expect(@sizeOf(ValueCount2) == 1);
try expect(@sizeOf(ValueCount256) == 1);
try expect(@sizeOf(ValueCount257) == 2);
}
}
test "enum literal equality" {
@ -1009,11 +687,7 @@ test "enum literal equality" {
}
test "enum literal cast to enum" {
const Color = enum {
Auto,
Off,
On,
};
const Color = enum { Auto, Off, On };
var color1: Color = .Auto;
var color2 = Color.Auto;
@ -1021,147 +695,8 @@ test "enum literal cast to enum" {
}
test "peer type resolution with enum literal" {
const Items = enum {
one,
two,
};
const Items = enum { one, two };
try expect(Items.two == .two);
try expect(.two == Items.two);
}
test "enum literal in array literal" {
const Items = enum {
one,
two,
};
const array = [_]Items{
.one,
.two,
};
try expect(array[0] == .one);
try expect(array[1] == .two);
}
test "signed integer as enum tag" {
const SignedEnum = enum(i2) {
A0 = -1,
A1 = 0,
A2 = 1,
};
try expect(@enumToInt(SignedEnum.A0) == -1);
try expect(@enumToInt(SignedEnum.A1) == 0);
try expect(@enumToInt(SignedEnum.A2) == 1);
}
test "enum value allocation" {
const LargeEnum = enum(u32) {
A0 = 0x80000000,
A1,
A2,
};
try expect(@enumToInt(LargeEnum.A0) == 0x80000000);
try expect(@enumToInt(LargeEnum.A1) == 0x80000001);
try expect(@enumToInt(LargeEnum.A2) == 0x80000002);
}
test "enum literal casting to tagged union" {
const Arch = union(enum) {
x86_64,
arm: Arm32,
const Arm32 = enum {
v8_5a,
v8_4a,
};
};
var t = true;
var x: Arch = .x86_64;
var y = if (t) x else .x86_64;
switch (y) {
.x86_64 => {},
else => @panic("fail"),
}
}
test "enum with one member and custom tag type" {
const E = enum(u2) {
One,
};
try expect(@enumToInt(E.One) == 0);
const E2 = enum(u2) {
One = 2,
};
try expect(@enumToInt(E2.One) == 2);
}
test "enum literal casting to optional" {
var bar: ?Bar = undefined;
bar = .B;
try expect(bar.? == Bar.B);
}
test "enum literal casting to error union with payload enum" {
var bar: error{B}!Bar = undefined;
bar = .B; // should never cast to the error set
try expect((try bar) == Bar.B);
}
test "enum with one member and u1 tag type @enumToInt" {
const Enum = enum(u1) {
Test,
};
try expect(@enumToInt(Enum.Test) == 0);
}
test "enum with comptime_int tag type" {
const Enum = enum(comptime_int) {
One = 3,
Two = 2,
Three = 1,
};
comptime try expect(Tag(Enum) == comptime_int);
}
test "enum with one member default to u0 tag type" {
const E0 = enum {
X,
};
comptime try expect(Tag(E0) == u0);
}
test "tagName on enum literals" {
try expect(mem.eql(u8, @tagName(.FooBar), "FooBar"));
comptime try expect(mem.eql(u8, @tagName(.FooBar), "FooBar"));
}
test "method call on an enum" {
const S = struct {
const E = enum {
one,
two,
fn method(self: *E) bool {
return self.* == .two;
}
fn generic_method(self: *E, foo: anytype) bool {
return self.* == .two and foo == bool;
}
};
fn doTheTest() !void {
var e = E.two;
try expect(e.method());
try expect(e.generic_method(bool));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

File diff suppressed because it is too large Load Diff

View File

@ -2,179 +2,3 @@ const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const mem = std.mem;
test "continue in for loop" {
const array = [_]i32{
1,
2,
3,
4,
5,
};
var sum: i32 = 0;
for (array) |x| {
sum += x;
if (x < 3) {
continue;
}
break;
}
if (sum != 6) unreachable;
}
test "for loop with pointer elem var" {
const source = "abcdefg";
var target: [source.len]u8 = undefined;
mem.copy(u8, target[0..], source);
mangleString(target[0..]);
try expect(mem.eql(u8, &target, "bcdefgh"));
for (source) |*c, i| {
_ = i;
try expect(@TypeOf(c) == *const u8);
}
for (target) |*c, i| {
_ = i;
try expect(@TypeOf(c) == *u8);
}
}
fn mangleString(s: []u8) void {
for (s) |*c| {
c.* += 1;
}
}
test "basic for loop" {
const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
var buffer: [expected_result.len]u8 = undefined;
var buf_index: usize = 0;
const array = [_]u8{ 9, 8, 7, 6 };
for (array) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (array) |item, index| {
_ = item;
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
const array_ptr = &array;
for (array_ptr) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (array_ptr) |item, index| {
_ = item;
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
const unknown_size: []const u8 = &array;
for (unknown_size) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (unknown_size) |_, index| {
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
try expect(mem.eql(u8, buffer[0..buf_index], &expected_result));
}
test "break from outer for loop" {
try testBreakOuter();
comptime try testBreakOuter();
}
fn testBreakOuter() !void {
var array = "aoeu";
var count: usize = 0;
outer: for (array) |_| {
for (array) |_| {
count += 1;
break :outer;
}
}
try expect(count == 1);
}
test "continue outer for loop" {
try testContinueOuter();
comptime try testContinueOuter();
}
fn testContinueOuter() !void {
var array = "aoeu";
var counter: usize = 0;
outer: for (array) |_| {
for (array) |_| {
counter += 1;
continue :outer;
}
}
try expect(counter == array.len);
}
test "2 break statements and an else" {
const S = struct {
fn entry(t: bool, f: bool) !void {
var buf: [10]u8 = undefined;
var ok = false;
ok = for (buf) |item| {
_ = item;
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "for with null and T peer types and inferred result location type" {
const S = struct {
fn doTheTest(slice: []const u8) !void {
if (for (slice) |item| {
if (item == 10) {
break item;
}
} else null) |v| {
_ = v;
@panic("fail");
}
}
};
try S.doTheTest(&[_]u8{ 1, 2 });
comptime try S.doTheTest(&[_]u8{ 1, 2 });
}
test "for copies its payload" {
const S = struct {
fn doTheTest() !void {
var x = [_]usize{ 1, 2, 3 };
for (x) |value, i| {
// Modify the original array
x[i] += 99;
try expectEqual(value, i + 1);
}
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "for on slice with allowzero ptr" {
const S = struct {
fn doTheTest(slice: []const u8) !void {
var ptr = @ptrCast([*]allowzero const u8, slice.ptr)[0..slice.len];
for (ptr) |x, i| try expect(x == i + 1);
for (ptr) |*x, i| try expect(x.* == i + 1);
}
};
try S.doTheTest(&[_]u8{ 1, 2, 3, 4 });
comptime try S.doTheTest(&[_]u8{ 1, 2, 3, 4 });
}

View File

@ -0,0 +1,185 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const mem = std.mem;
test "continue in for loop" {
const array = [_]i32{ 1, 2, 3, 4, 5 };
var sum: i32 = 0;
for (array) |x| {
sum += x;
if (x < 3) {
continue;
}
break;
}
if (sum != 6) unreachable;
}
test "for loop with pointer elem var" {
const source = "abcdefg";
var target: [source.len]u8 = undefined;
mem.copy(u8, target[0..], source);
mangleString(target[0..]);
try expect(mem.eql(u8, &target, "bcdefgh"));
for (source) |*c, i| {
_ = i;
try expect(@TypeOf(c) == *const u8);
}
for (target) |*c, i| {
_ = i;
try expect(@TypeOf(c) == *u8);
}
}
fn mangleString(s: []u8) void {
for (s) |*c| {
c.* += 1;
}
}
test "basic for loop" {
const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3;
var buffer: [expected_result.len]u8 = undefined;
var buf_index: usize = 0;
const array = [_]u8{ 9, 8, 7, 6 };
for (array) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (array) |item, index| {
_ = item;
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
const array_ptr = &array;
for (array_ptr) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (array_ptr) |item, index| {
_ = item;
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
const unknown_size: []const u8 = &array;
for (unknown_size) |item| {
buffer[buf_index] = item;
buf_index += 1;
}
for (unknown_size) |_, index| {
buffer[buf_index] = @intCast(u8, index);
buf_index += 1;
}
try expect(mem.eql(u8, buffer[0..buf_index], &expected_result));
}
test "break from outer for loop" {
try testBreakOuter();
comptime try testBreakOuter();
}
fn testBreakOuter() !void {
var array = "aoeu";
var count: usize = 0;
outer: for (array) |_| {
for (array) |_| {
count += 1;
break :outer;
}
}
try expect(count == 1);
}
test "continue outer for loop" {
try testContinueOuter();
comptime try testContinueOuter();
}
fn testContinueOuter() !void {
var array = "aoeu";
var counter: usize = 0;
outer: for (array) |_| {
for (array) |_| {
counter += 1;
continue :outer;
}
}
try expect(counter == array.len);
}
test "2 break statements and an else" {
const S = struct {
fn entry(t: bool, f: bool) !void {
var buf: [10]u8 = undefined;
var ok = false;
ok = for (buf) |item| {
_ = item;
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "for with null and T peer types and inferred result location type" {
const S = struct {
fn doTheTest(slice: []const u8) !void {
if (for (slice) |item| {
if (item == 10) {
break item;
}
} else null) |v| {
_ = v;
@panic("fail");
}
}
};
try S.doTheTest(&[_]u8{ 1, 2 });
comptime try S.doTheTest(&[_]u8{ 1, 2 });
}
test "for copies its payload" {
const S = struct {
fn doTheTest() !void {
var x = [_]usize{ 1, 2, 3 };
for (x) |value, i| {
// Modify the original array
x[i] += 99;
try expectEqual(value, i + 1);
}
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "for on slice with allowzero ptr" {
const S = struct {
fn doTheTest(slice: []const u8) !void {
var ptr = @ptrCast([*]allowzero const u8, slice.ptr)[0..slice.len];
for (ptr) |x, i| try expect(x == i + 1);
for (ptr) |*x, i| try expect(x.* == i + 1);
}
};
try S.doTheTest(&[_]u8{ 1, 2, 3, 4 });
comptime try S.doTheTest(&[_]u8{ 1, 2, 3, 4 });
}
test "ignore lval with underscore (for loop)" {
for ([_]void{}) |_, i| {
_ = i;
for ([_]void{}) |_, j| {
_ = j;
break;
}
break;
}
}

View File

@ -73,3 +73,18 @@ test "const result loc, runtime if cond, else unreachable" {
const x = if (t) Num.Two else unreachable;
try expect(x == .Two);
}
test "if copies its payload" {
const S = struct {
fn doTheTest() !void {
var tmp: ?i32 = 10;
if (tmp) |value| {
// Modify the original variable
tmp = null;
try expect(value == 10);
} else unreachable;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -17,18 +17,3 @@ test "if prongs cast to expected type instead of peer type resolution" {
try S.doTheTest(false);
comptime try S.doTheTest(false);
}
test "while copies its payload" {
const S = struct {
fn doTheTest() !void {
var tmp: ?i32 = 10;
if (tmp) |value| {
// Modify the original variable
tmp = null;
try expectEqual(@as(i32, 10), value);
} else unreachable;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}

View File

@ -2,548 +2,3 @@ const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const expectEqual = std.testing.expectEqual;
test "switch with numbers" {
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" {
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 "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" {
const fruit = Fruit.Orange;
nonConstSwitchOnEnum(fruit);
}
const Fruit = enum {
Apple,
Orange,
Banana,
};
fn nonConstSwitchOnEnum(fruit: Fruit) void {
switch (fruit) {
Fruit.Apple => unreachable,
Fruit.Orange => {},
Fruit.Banana => unreachable,
}
}
test "switch statement" {
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 prong with variable" {
try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
}
const SwitchProngWithVarEnum = union(enum) {
One: i32,
Two: f32,
Meh: void,
};
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" {
try testSwitchEnumPtrCapture();
comptime try 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 with multiple expressions" {
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;
}
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 "switch on type" {
try expect(trueIfBoolFalseOtherwise(bool));
try expect(!trueIfBoolFalseOtherwise(i32));
}
fn trueIfBoolFalseOtherwise(comptime T: type) bool {
return switch (T) {
bool => true,
else => false,
};
}
test "switch handles all cases of number" {
try testSwitchHandleAllCases();
comptime try 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 all prongs unreachable" {
try testAllProngsUnreachable();
comptime try 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;
}
fn return_a_number() anyerror!i32 {
return 1;
}
test "capture value of switch with all unreachable prongs" {
const x = return_a_number() catch |err| switch (err) {
else => unreachable,
};
try expect(x == 1);
}
test "switching on booleans" {
try testSwitchOnBools();
comptime try 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;
switch (val) {
0 => try expect(val == 0),
}
}
test "undefined.u0" {
var val: u0 = undefined;
switch (val) {
0 => try expect(val == 0),
}
}
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 {
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();
comptime try S.doTheTest();
}
test "switch prongs with error set cases make a new error set type for capture value" {
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();
comptime try S.doTheTest();
}
test "return result loc and then switch with range implicit casted to error union" {
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();
comptime try S.doTheTest();
}
test "switch with null and T peer types and inferred result location type" {
const S = struct {
fn doTheTest(c: u8) !void {
if (switch (c) {
0 => true,
else => null,
}) |v| {
_ = v;
@panic("fail");
}
}
};
try S.doTheTest(1);
comptime try S.doTheTest(1);
}
test "switch prongs with cases with identical payload types" {
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| {
try expect(@TypeOf(e) == usize);
try expect(e == 8);
},
.B => |e| {
_ = e;
@panic("fail");
},
}
}
fn doTheSwitch2(u: Union) !void {
switch (u) {
.A, .C => |e| {
_ = e;
@panic("fail");
},
.B => |e| {
try expect(@TypeOf(e) == isize);
try expect(e == -8);
},
}
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "switch with disjoint range" {
var q: u8 = 0;
switch (q) {
0...125 => {},
127...255 => {},
126...126 => {},
}
}
test "switch variable for range and multiple prongs" {
const S = struct {
fn doTheTest() !void {
var u: u8 = 16;
try doTheSwitch(u);
comptime try doTheSwitch(u);
var v: u8 = 42;
try doTheSwitch(v);
comptime try doTheSwitch(v);
}
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),
}
}
};
_ = S;
}
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" {
while (state < 2) {
poll();
}
}
test "switch on pointer type" {
const S = struct {
const X = struct {
field: u32,
};
const P1 = @intToPtr(*X, 0x400);
const P2 = @intToPtr(*X, 0x800);
const P3 = @intToPtr(*X, 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 try expect(1 == S.doTheTest(S.P1));
comptime try expect(2 == S.doTheTest(S.P2));
comptime try expect(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;
try expect(switch (some) {
else => |a| blk: {
a catch {};
break :blk true;
},
});
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "while copies its payload" {
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();
comptime try S.doTheTest();
}

View File

@ -0,0 +1,549 @@
const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const expectEqual = std.testing.expectEqual;
test "switch with numbers" {
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" {
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 "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" {
const fruit = Fruit.Orange;
nonConstSwitchOnEnum(fruit);
}
const Fruit = enum {
Apple,
Orange,
Banana,
};
fn nonConstSwitchOnEnum(fruit: Fruit) void {
switch (fruit) {
Fruit.Apple => unreachable,
Fruit.Orange => {},
Fruit.Banana => unreachable,
}
}
test "switch statement" {
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 prong with variable" {
try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
}
const SwitchProngWithVarEnum = union(enum) {
One: i32,
Two: f32,
Meh: void,
};
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" {
try testSwitchEnumPtrCapture();
comptime try 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 with multiple expressions" {
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;
}
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 "switch on type" {
try expect(trueIfBoolFalseOtherwise(bool));
try expect(!trueIfBoolFalseOtherwise(i32));
}
fn trueIfBoolFalseOtherwise(comptime T: type) bool {
return switch (T) {
bool => true,
else => false,
};
}
test "switch handles all cases of number" {
try testSwitchHandleAllCases();
comptime try 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 all prongs unreachable" {
try testAllProngsUnreachable();
comptime try 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;
}
fn return_a_number() anyerror!i32 {
return 1;
}
test "capture value of switch with all unreachable prongs" {
const x = return_a_number() catch |err| switch (err) {
else => unreachable,
};
try expect(x == 1);
}
test "switching on booleans" {
try testSwitchOnBools();
comptime try 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;
switch (val) {
0 => try expect(val == 0),
}
}
test "undefined.u0" {
var val: u0 = undefined;
switch (val) {
0 => try expect(val == 0),
}
}
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 {
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();
comptime try S.doTheTest();
}
test "switch prongs with error set cases make a new error set type for capture value" {
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();
comptime try S.doTheTest();
}
test "return result loc and then switch with range implicit casted to error union" {
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();
comptime try S.doTheTest();
}
test "switch with null and T peer types and inferred result location type" {
const S = struct {
fn doTheTest(c: u8) !void {
if (switch (c) {
0 => true,
else => null,
}) |v| {
_ = v;
@panic("fail");
}
}
};
try S.doTheTest(1);
comptime try S.doTheTest(1);
}
test "switch prongs with cases with identical payload types" {
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| {
try expect(@TypeOf(e) == usize);
try expect(e == 8);
},
.B => |e| {
_ = e;
@panic("fail");
},
}
}
fn doTheSwitch2(u: Union) !void {
switch (u) {
.A, .C => |e| {
_ = e;
@panic("fail");
},
.B => |e| {
try expect(@TypeOf(e) == isize);
try expect(e == -8);
},
}
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "switch with disjoint range" {
var q: u8 = 0;
switch (q) {
0...125 => {},
127...255 => {},
126...126 => {},
}
}
test "switch variable for range and multiple prongs" {
const S = struct {
fn doTheTest() !void {
var u: u8 = 16;
try doTheSwitch(u);
comptime try doTheSwitch(u);
var v: u8 = 42;
try doTheSwitch(v);
comptime try doTheSwitch(v);
}
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),
}
}
};
_ = S;
}
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" {
while (state < 2) {
poll();
}
}
test "switch on pointer type" {
const S = struct {
const X = struct {
field: u32,
};
const P1 = @intToPtr(*X, 0x400);
const P2 = @intToPtr(*X, 0x800);
const P3 = @intToPtr(*X, 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 try expect(1 == S.doTheTest(S.P1));
comptime try expect(2 == S.doTheTest(S.P2));
comptime try expect(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;
try expect(switch (some) {
else => |a| blk: {
a catch {};
break :blk true;
},
});
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "while copies its payload" {
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();
comptime try S.doTheTest();
}

View File

@ -5,17 +5,6 @@ test "ignore lval with underscore" {
_ = false;
}
test "ignore lval with underscore (for loop)" {
for ([_]void{}) |_, i| {
_ = i;
for ([_]void{}) |_, j| {
_ = j;
break;
}
break;
}
}
test "ignore lval with underscore (while loop)" {
while (optionalReturnError()) |_| {
while (optionalReturnError()) |_| {

View File

@ -23,7 +23,7 @@ test "static eval while" {
}
const static_eval_while_number = staticWhileLoop1();
fn staticWhileLoop1() i32 {
return whileLoop2();
return staticWhileLoop2();
}
fn staticWhileLoop2() i32 {
while (true) {
@ -31,33 +31,6 @@ fn staticWhileLoop2() i32 {
}
}
test "continue and break" {
try runContinueAndBreakTest();
try expect(continue_and_break_counter == 8);
}
var continue_and_break_counter: i32 = 0;
fn runContinueAndBreakTest() !void {
var i: i32 = 0;
while (true) {
continue_and_break_counter += 2;
i += 1;
if (i < 4) {
continue;
}
break;
}
try expect(i == 4);
}
test "return with implicit cast from while loop" {
returnWithImplicitCastFromWhileLoopTest() catch unreachable;
}
fn returnWithImplicitCastFromWhileLoopTest() anyerror!void {
while (true) {
return;
}
}
test "while with continue expression" {
var sum: i32 = 0;
{
@ -83,43 +56,6 @@ test "while with else" {
try expect(got_else == 1);
}
test "while with optional as condition" {
numbers_left = 10;
var sum: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
}
try expect(sum == 45);
}
test "while with optional as condition with else" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
try expect(got_else == 0);
} else {
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
test "while with error union condition" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrErr()) |value| {
sum += value;
} else |err| {
try expect(err == error.OutOfNumbers);
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
var numbers_left: i32 = undefined;
fn getNumberOrErr() anyerror!i32 {
return if (numbers_left == 0) error.OutOfNumbers else x: {
@ -134,61 +70,6 @@ fn getNumberOrNull() ?i32 {
};
}
test "while on optional with else result follow else prong" {
const result = while (returnNull()) |value| {
break value;
} else @as(i32, 2);
try expect(result == 2);
}
test "while on optional with else result follow break prong" {
const result = while (returnOptional(10)) |value| {
break value;
} else @as(i32, 2);
try expect(result == 10);
}
test "while on error union with else result follow else prong" {
const result = while (returnError()) |value| {
break value;
} else |_| @as(i32, 2);
try expect(result == 2);
}
test "while on error union with else result follow break prong" {
const result = while (returnSuccess(10)) |value| {
break value;
} else |_| @as(i32, 2);
try expect(result == 10);
}
test "while on bool with else result follow else prong" {
const result = while (returnFalse()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 2);
}
test "while on bool with else result follow break prong" {
const result = while (returnTrue()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 10);
}
test "break from outer while loop" {
testBreakOuter();
comptime testBreakOuter();
}
fn testBreakOuter() void {
outer: while (true) {
while (true) {
break :outer;
}
}
}
test "continue outer while loop" {
testContinueOuter();
comptime testContinueOuter();
@ -203,68 +84,17 @@ fn testContinueOuter() void {
}
}
fn returnNull() ?i32 {
return null;
}
fn returnOptional(x: i32) ?i32 {
return x;
}
fn returnError() anyerror!i32 {
return error.YouWantedAnError;
}
fn returnSuccess(x: i32) anyerror!i32 {
return x;
}
fn returnFalse() bool {
return false;
}
fn returnTrue() bool {
return true;
test "break from outer while loop" {
testBreakOuter();
comptime testBreakOuter();
}
test "while bool 2 break statements and an else" {
const S = struct {
fn entry(t: bool, f: bool) !void {
var ok = false;
ok = while (t) {
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
fn testBreakOuter() void {
outer: while (true) {
while (true) {
break :outer;
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "while optional 2 break statements and an else" {
const S = struct {
fn entry(opt_t: ?bool, f: bool) !void {
var ok = false;
ok = while (opt_t) |t| {
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "while error 2 break statements and an else" {
const S = struct {
fn entry(opt_t: anyerror!bool, f: bool) !void {
var ok = false;
ok = while (opt_t) |t| {
if (f) break false;
if (t) break true;
} else |_| false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
}
test "while copies its payload" {

View File

@ -0,0 +1,186 @@
const std = @import("std");
const expect = std.testing.expect;
test "continue and break" {
try runContinueAndBreakTest();
try expect(continue_and_break_counter == 8);
}
var continue_and_break_counter: i32 = 0;
fn runContinueAndBreakTest() !void {
var i: i32 = 0;
while (true) {
continue_and_break_counter += 2;
i += 1;
if (i < 4) {
continue;
}
break;
}
try expect(i == 4);
}
test "return with implicit cast from while loop" {
returnWithImplicitCastFromWhileLoopTest() catch unreachable;
}
fn returnWithImplicitCastFromWhileLoopTest() anyerror!void {
while (true) {
return;
}
}
test "while with optional as condition" {
numbers_left = 10;
var sum: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
}
try expect(sum == 45);
}
test "while with optional as condition with else" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
try expect(got_else == 0);
} else {
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
test "while with error union condition" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrErr()) |value| {
sum += value;
} else |err| {
try expect(err == error.OutOfNumbers);
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
var numbers_left: i32 = undefined;
fn getNumberOrErr() anyerror!i32 {
return if (numbers_left == 0) error.OutOfNumbers else x: {
numbers_left -= 1;
break :x numbers_left;
};
}
fn getNumberOrNull() ?i32 {
return if (numbers_left == 0) null else x: {
numbers_left -= 1;
break :x numbers_left;
};
}
test "while on optional with else result follow else prong" {
const result = while (returnNull()) |value| {
break value;
} else @as(i32, 2);
try expect(result == 2);
}
test "while on optional with else result follow break prong" {
const result = while (returnOptional(10)) |value| {
break value;
} else @as(i32, 2);
try expect(result == 10);
}
test "while on error union with else result follow else prong" {
const result = while (returnError()) |value| {
break value;
} else |_| @as(i32, 2);
try expect(result == 2);
}
test "while on error union with else result follow break prong" {
const result = while (returnSuccess(10)) |value| {
break value;
} else |_| @as(i32, 2);
try expect(result == 10);
}
test "while on bool with else result follow else prong" {
const result = while (returnFalse()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 2);
}
test "while on bool with else result follow break prong" {
const result = while (returnTrue()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 10);
}
fn returnNull() ?i32 {
return null;
}
fn returnOptional(x: i32) ?i32 {
return x;
}
fn returnError() anyerror!i32 {
return error.YouWantedAnError;
}
fn returnSuccess(x: i32) anyerror!i32 {
return x;
}
fn returnFalse() bool {
return false;
}
fn returnTrue() bool {
return true;
}
test "while bool 2 break statements and an else" {
const S = struct {
fn entry(t: bool, f: bool) !void {
var ok = false;
ok = while (t) {
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "while optional 2 break statements and an else" {
const S = struct {
fn entry(opt_t: ?bool, f: bool) !void {
var ok = false;
ok = while (opt_t) |t| {
if (f) break false;
if (t) break true;
} else false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}
test "while error 2 break statements and an else" {
const S = struct {
fn entry(opt_t: anyerror!bool, f: bool) !void {
var ok = false;
ok = while (opt_t) |t| {
if (f) break false;
if (t) break true;
} else |_| false;
try expect(ok);
}
};
try S.entry(true, false);
comptime try S.entry(true, false);
}