From c4df9bf56f204f63f4e87255933ba453d69d0182 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 Oct 2021 20:15:03 -0700 Subject: [PATCH] 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. --- src/AstGen.zig | 19 +- src/codegen/llvm.zig | 10 +- test/behavior.zig | 22 +- test/behavior/call.zig | 71 -- test/behavior/call_stage1.zig | 74 ++ test/behavior/cast.zig | 49 + test/behavior/cast_stage1.zig | 60 +- test/behavior/const_slice_child.zig | 6 +- test/behavior/defer.zig | 110 -- test/behavior/defer_stage1.zig | 114 ++ test/behavior/enum.zig | 525 +------- test/behavior/enum_stage1.zig | 1058 +++++++++++++++++ test/behavior/for.zig | 176 --- test/behavior/for_stage1.zig | 185 +++ test/behavior/if.zig | 15 + test/behavior/if_stage1.zig | 15 - test/behavior/switch.zig | 545 --------- test/behavior/switch_stage1.zig | 549 +++++++++ test/behavior/underscore.zig | 11 - ...ith_members.zig => union_with_members.zig} | 0 test/behavior/while.zig | 188 +-- test/behavior/while_stage1.zig | 186 +++ 22 files changed, 2309 insertions(+), 1679 deletions(-) create mode 100644 test/behavior/call_stage1.zig create mode 100644 test/behavior/defer_stage1.zig create mode 100644 test/behavior/enum_stage1.zig create mode 100644 test/behavior/for_stage1.zig create mode 100644 test/behavior/switch_stage1.zig rename test/behavior/{enum_with_members.zig => union_with_members.zig} (100%) create mode 100644 test/behavior/while_stage1.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 54534b1e5a..79608fedf3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 761dd2a8bc..500aa482fa 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -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; } diff --git a/test/behavior.zig b/test/behavior.zig index 1855ff5cf7..2a16abe780 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -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"); } diff --git a/test/behavior/call.zig b/test/behavior/call.zig index f036c3c7c3..03c8038ffa 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -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)); -} diff --git a/test/behavior/call_stage1.zig b/test/behavior/call_stage1.zig new file mode 100644 index 0000000000..f036c3c7c3 --- /dev/null +++ b/test/behavior/call_stage1.zig @@ -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)); +} diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 6a6f02908a..3b6b5a02e7 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -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); +} diff --git a/test/behavior/cast_stage1.zig b/test/behavior/cast_stage1.zig index d924dfde6c..0705047937 100644 --- a/test/behavior/cast_stage1.zig +++ b/test/behavior/cast_stage1.zig @@ -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) { diff --git a/test/behavior/const_slice_child.zig b/test/behavior/const_slice_child.zig index 0fa9d71ee6..ecce612d1d 100644 --- a/test/behavior/const_slice_child.zig +++ b/test/behavior/const_slice_child.zig @@ -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); } diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig index 53ce188928..f340fd1842 100644 --- a/test/behavior/defer.zig +++ b/test/behavior/defer.zig @@ -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(); -} diff --git a/test/behavior/defer_stage1.zig b/test/behavior/defer_stage1.zig new file mode 100644 index 0000000000..53ce188928 --- /dev/null +++ b/test/behavior/defer_stage1.zig @@ -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(); +} diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index fd526e1bef..1324fa0e4a 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -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(); -} diff --git a/test/behavior/enum_stage1.zig b/test/behavior/enum_stage1.zig new file mode 100644 index 0000000000..c34a3b1264 --- /dev/null +++ b/test/behavior/enum_stage1.zig @@ -0,0 +1,1058 @@ +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 Point = struct { + x: u64, + y: u64, +}; +const Foo = union(enum) { + One: i32, + Two: Point, + Three: void, +}; +const FooNoVoid = union(enum) { + One: i32, + Two: Point, +}; +const Bar = enum { + A, + B, + C, + D, +}; + +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 => {}, + else => unreachable, + } +} + +fn shouldBeNotEmpty(x: AnEnumWithPayload) void { + switch (x) { + AnEnumWithPayload.Empty => unreachable, + else => {}, + } +} + +const AnEnumWithPayload = union(enum) { + Empty: void, + Full: i32, +}; + +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 NonExhaustive = enum(u8) { A, B, _ }; + +test "enum alignment" { + comptime { + try expect(@alignOf(AlignTestEnum) >= @alignOf([9]u8)); + try expect(@alignOf(AlignTestEnum) >= @alignOf(u64)); + } +} + +const AlignTestEnum = union(enum) { + A: [9]u8, + B: u64, +}; + +const ValueCount1 = enum { + I0, +}; +const ValueCount2 = enum { + I0, + I1, +}; +const ValueCount256 = enum { + I0, + I1, + I2, + I3, + I4, + I5, + I6, + I7, + I8, + I9, + I10, + I11, + I12, + I13, + I14, + I15, + I16, + I17, + I18, + I19, + I20, + I21, + I22, + I23, + I24, + I25, + I26, + I27, + I28, + I29, + I30, + I31, + I32, + I33, + I34, + I35, + I36, + I37, + I38, + I39, + I40, + I41, + I42, + I43, + I44, + I45, + I46, + I47, + I48, + I49, + I50, + I51, + I52, + I53, + I54, + I55, + I56, + I57, + I58, + I59, + I60, + I61, + I62, + I63, + I64, + I65, + I66, + I67, + I68, + I69, + I70, + I71, + I72, + I73, + I74, + I75, + I76, + I77, + I78, + I79, + I80, + I81, + I82, + I83, + I84, + I85, + I86, + I87, + I88, + I89, + I90, + I91, + I92, + I93, + I94, + I95, + I96, + I97, + I98, + I99, + I100, + I101, + I102, + I103, + I104, + I105, + I106, + I107, + I108, + I109, + I110, + I111, + I112, + I113, + I114, + I115, + I116, + I117, + I118, + I119, + I120, + I121, + I122, + I123, + I124, + I125, + I126, + I127, + I128, + I129, + I130, + I131, + I132, + I133, + I134, + I135, + I136, + I137, + I138, + I139, + I140, + I141, + I142, + I143, + I144, + I145, + I146, + I147, + I148, + I149, + I150, + I151, + I152, + I153, + I154, + I155, + I156, + I157, + I158, + I159, + I160, + I161, + I162, + I163, + I164, + I165, + I166, + I167, + I168, + I169, + I170, + I171, + I172, + I173, + I174, + I175, + I176, + I177, + I178, + I179, + I180, + I181, + I182, + I183, + I184, + I185, + I186, + I187, + I188, + I189, + I190, + I191, + I192, + I193, + I194, + I195, + I196, + I197, + I198, + I199, + I200, + I201, + I202, + I203, + I204, + I205, + I206, + I207, + I208, + I209, + I210, + I211, + I212, + I213, + I214, + I215, + I216, + I217, + I218, + I219, + I220, + I221, + I222, + I223, + I224, + I225, + I226, + I227, + I228, + I229, + I230, + I231, + I232, + I233, + I234, + I235, + I236, + I237, + I238, + I239, + I240, + I241, + I242, + I243, + I244, + I245, + I246, + I247, + I248, + I249, + I250, + I251, + I252, + I253, + I254, + I255, +}; +const ValueCount257 = enum { + I0, + I1, + I2, + I3, + I4, + I5, + I6, + I7, + I8, + I9, + I10, + I11, + I12, + I13, + I14, + I15, + I16, + I17, + I18, + I19, + I20, + I21, + I22, + I23, + I24, + I25, + I26, + I27, + I28, + I29, + I30, + I31, + I32, + I33, + I34, + I35, + I36, + I37, + I38, + I39, + I40, + I41, + I42, + I43, + I44, + I45, + I46, + I47, + I48, + I49, + I50, + I51, + I52, + I53, + I54, + I55, + I56, + I57, + I58, + I59, + I60, + I61, + I62, + I63, + I64, + I65, + I66, + I67, + I68, + I69, + I70, + I71, + I72, + I73, + I74, + I75, + I76, + I77, + I78, + I79, + I80, + I81, + I82, + I83, + I84, + I85, + I86, + I87, + I88, + I89, + I90, + I91, + I92, + I93, + I94, + I95, + I96, + I97, + I98, + I99, + I100, + I101, + I102, + I103, + I104, + I105, + I106, + I107, + I108, + I109, + I110, + I111, + I112, + I113, + I114, + I115, + I116, + I117, + I118, + I119, + I120, + I121, + I122, + I123, + I124, + I125, + I126, + I127, + I128, + I129, + I130, + I131, + I132, + I133, + I134, + I135, + I136, + I137, + I138, + I139, + I140, + I141, + I142, + I143, + I144, + I145, + I146, + I147, + I148, + I149, + I150, + I151, + I152, + I153, + I154, + I155, + I156, + I157, + I158, + I159, + I160, + I161, + I162, + I163, + I164, + I165, + I166, + I167, + I168, + I169, + I170, + I171, + I172, + I173, + I174, + I175, + I176, + I177, + I178, + I179, + I180, + I181, + I182, + I183, + I184, + I185, + I186, + I187, + I188, + I189, + I190, + I191, + I192, + I193, + I194, + I195, + I196, + I197, + I198, + I199, + I200, + I201, + I202, + I203, + I204, + I205, + I206, + I207, + I208, + I209, + I210, + I211, + I212, + I213, + I214, + I215, + I216, + I217, + I218, + I219, + I220, + I221, + I222, + I223, + I224, + I225, + I226, + I227, + I228, + I229, + I230, + I231, + I232, + I233, + I234, + I235, + I236, + I237, + I238, + I239, + I240, + I241, + I242, + I243, + I244, + I245, + I246, + I247, + I248, + I249, + I250, + I251, + I252, + I253, + I254, + I255, + I256, +}; + +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 BitFieldOfEnums = packed struct { + a: A, + b: B, + c: C, +}; + +const bit_field_1 = BitFieldOfEnums{ + .a = A.Two, + .b = B.Three3, + .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; +} + +fn getB(data: *const BitFieldOfEnums) B { + return data.b; +} + +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, + C = 60, + 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, + Unspecified2, + B = 40, + Unspecified3, + C = 60, + Unspecified4, + D = 1000, + 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 }; + +fn doALoopThing(id: EnumWithOneMember) void { + while (true) { + if (id == EnumWithOneMember.Eof) { + break; + } + @compileError("above if condition should be comptime"); + } +} + +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 EnumWithTagValues = enum(u4) { + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, + D = 1 << 3, +}; +test "enum with tag values don't require parens" { + try expect(@enumToInt(EnumWithTagValues.C) == 0b0100); +} + +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 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(); +} diff --git a/test/behavior/for.zig b/test/behavior/for.zig index b022df0958..0361451138 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -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 }); -} diff --git a/test/behavior/for_stage1.zig b/test/behavior/for_stage1.zig new file mode 100644 index 0000000000..13b50892f9 --- /dev/null +++ b/test/behavior/for_stage1.zig @@ -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; + } +} diff --git a/test/behavior/if.zig b/test/behavior/if.zig index a1f722d827..e907f288de 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -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(); +} diff --git a/test/behavior/if_stage1.zig b/test/behavior/if_stage1.zig index 8b299deaec..df1f3f3201 100644 --- a/test/behavior/if_stage1.zig +++ b/test/behavior/if_stage1.zig @@ -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(); -} diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 62afc74d83..9524e69540 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -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(); -} diff --git a/test/behavior/switch_stage1.zig b/test/behavior/switch_stage1.zig new file mode 100644 index 0000000000..62afc74d83 --- /dev/null +++ b/test/behavior/switch_stage1.zig @@ -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(); +} diff --git a/test/behavior/underscore.zig b/test/behavior/underscore.zig index abe219f3e0..b701aac044 100644 --- a/test/behavior/underscore.zig +++ b/test/behavior/underscore.zig @@ -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()) |_| { diff --git a/test/behavior/enum_with_members.zig b/test/behavior/union_with_members.zig similarity index 100% rename from test/behavior/enum_with_members.zig rename to test/behavior/union_with_members.zig diff --git a/test/behavior/while.zig b/test/behavior/while.zig index e52adfdbcf..a2f34d54c0 100644 --- a/test/behavior/while.zig +++ b/test/behavior/while.zig @@ -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" { diff --git a/test/behavior/while_stage1.zig b/test/behavior/while_stage1.zig new file mode 100644 index 0000000000..dc83d5d163 --- /dev/null +++ b/test/behavior/while_stage1.zig @@ -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); +}