diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-04-29 15:54:04 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-04-29 15:54:04 -0700 |
| commit | 4307436b9945f814ff5731981df1d19febf3ba0a (patch) | |
| tree | efaaec94a41d632d45f255d464d9787eb02c4c9b /test/behavior | |
| parent | 5a02c938dafdf2bb11b2350b6ad3161ef93744f0 (diff) | |
| download | zig-4307436b9945f814ff5731981df1d19febf3ba0a.tar.gz zig-4307436b9945f814ff5731981df1d19febf3ba0a.zip | |
move behavior tests from test/stage1/ to test/
And fix test cases to make them pass. This is in preparation for
starting to pass behavior tests with self-hosted.
Diffstat (limited to 'test/behavior')
149 files changed, 18317 insertions, 0 deletions
diff --git a/test/behavior/align.zig b/test/behavior/align.zig new file mode 100644 index 0000000000..3ef147746c --- /dev/null +++ b/test/behavior/align.zig @@ -0,0 +1,347 @@ +const std = @import("std"); +const expect = std.testing.expect; +const builtin = @import("builtin"); +const native_arch = builtin.target.cpu.arch; + +var foo: u8 align(4) = 100; + +test "global variable alignment" { + comptime expect(@typeInfo(@TypeOf(&foo)).Pointer.alignment == 4); + comptime expect(@TypeOf(&foo) == *align(4) u8); + { + const slice = @as(*[1]u8, &foo)[0..]; + comptime expect(@TypeOf(slice) == *align(4) [1]u8); + } + { + var runtime_zero: usize = 0; + const slice = @as(*[1]u8, &foo)[runtime_zero..]; + comptime expect(@TypeOf(slice) == []align(4) u8); + } +} + +fn derp() align(@sizeOf(usize) * 2) i32 { + return 1234; +} +fn noop1() align(1) void {} +fn noop4() align(4) void {} + +test "function alignment" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + expect(derp() == 1234); + expect(@TypeOf(noop1) == fn () align(1) void); + expect(@TypeOf(noop4) == fn () align(4) void); + noop1(); + noop4(); +} + +var baz: packed struct { + a: u32, + b: u32, +} = undefined; + +test "packed struct alignment" { + expect(@TypeOf(&baz.b) == *align(1) u32); +} + +const blah: packed struct { + a: u3, + b: u3, + c: u2, +} = undefined; + +test "bit field alignment" { + expect(@TypeOf(&blah.b) == *align(1:3:1) const u3); +} + +test "default alignment allows unspecified in type syntax" { + expect(*u32 == *align(@alignOf(u32)) u32); +} + +test "implicitly decreasing pointer alignment" { + const a: u32 align(4) = 3; + const b: u32 align(8) = 4; + expect(addUnaligned(&a, &b) == 7); +} + +fn addUnaligned(a: *align(1) const u32, b: *align(1) const u32) u32 { + return a.* + b.*; +} + +test "implicitly decreasing slice alignment" { + const a: u32 align(4) = 3; + const b: u32 align(8) = 4; + expect(addUnalignedSlice(@as(*const [1]u32, &a)[0..], @as(*const [1]u32, &b)[0..]) == 7); +} +fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { + return a[0] + b[0]; +} + +test "specifying alignment allows pointer cast" { + testBytesAlign(0x33); +} +fn testBytesAlign(b: u8) void { + var bytes align(4) = [_]u8{ + b, + b, + b, + b, + }; + const ptr = @ptrCast(*u32, &bytes[0]); + expect(ptr.* == 0x33333333); +} + +test "@alignCast pointers" { + var x: u32 align(4) = 1; + expectsOnly1(&x); + expect(x == 2); +} +fn expectsOnly1(x: *align(1) u32) void { + expects4(@alignCast(4, x)); +} +fn expects4(x: *align(4) u32) void { + x.* += 1; +} + +test "@alignCast slices" { + var array align(4) = [_]u32{ + 1, + 1, + }; + const slice = array[0..]; + sliceExpectsOnly1(slice); + expect(slice[0] == 2); +} +fn sliceExpectsOnly1(slice: []align(1) u32) void { + sliceExpects4(@alignCast(4, slice)); +} +fn sliceExpects4(slice: []align(4) u32) void { + slice[0] += 1; +} + +test "implicitly decreasing fn alignment" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + testImplicitlyDecreaseFnAlign(alignedSmall, 1234); + testImplicitlyDecreaseFnAlign(alignedBig, 5678); +} + +fn testImplicitlyDecreaseFnAlign(ptr: fn () align(1) i32, answer: i32) void { + expect(ptr() == answer); +} + +fn alignedSmall() align(8) i32 { + return 1234; +} +fn alignedBig() align(16) i32 { + return 5678; +} + +test "@alignCast functions" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + expect(fnExpectsOnly1(simple4) == 0x19); +} +fn fnExpectsOnly1(ptr: fn () align(1) i32) i32 { + return fnExpects4(@alignCast(4, ptr)); +} +fn fnExpects4(ptr: fn () align(4) i32) i32 { + return ptr(); +} +fn simple4() align(4) i32 { + return 0x19; +} + +test "generic function with align param" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + expect(whyWouldYouEverDoThis(1) == 0x1); + expect(whyWouldYouEverDoThis(4) == 0x1); + expect(whyWouldYouEverDoThis(8) == 0x1); +} + +fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) u8 { + return 0x1; +} + +test "@ptrCast preserves alignment of bigger source" { + var x: u32 align(16) = 1234; + const ptr = @ptrCast(*u8, &x); + expect(@TypeOf(ptr) == *align(16) u8); +} + +test "runtime known array index has best alignment possible" { + // take full advantage of over-alignment + var array align(4) = [_]u8{ 1, 2, 3, 4 }; + expect(@TypeOf(&array[0]) == *align(4) u8); + expect(@TypeOf(&array[1]) == *u8); + expect(@TypeOf(&array[2]) == *align(2) u8); + expect(@TypeOf(&array[3]) == *u8); + + // because align is too small but we still figure out to use 2 + var bigger align(2) = [_]u64{ 1, 2, 3, 4 }; + expect(@TypeOf(&bigger[0]) == *align(2) u64); + expect(@TypeOf(&bigger[1]) == *align(2) u64); + expect(@TypeOf(&bigger[2]) == *align(2) u64); + expect(@TypeOf(&bigger[3]) == *align(2) u64); + + // because pointer is align 2 and u32 align % 2 == 0 we can assume align 2 + var smaller align(2) = [_]u32{ 1, 2, 3, 4 }; + var runtime_zero: usize = 0; + comptime expect(@TypeOf(smaller[runtime_zero..]) == []align(2) u32); + comptime expect(@TypeOf(smaller[runtime_zero..].ptr) == [*]align(2) u32); + testIndex(smaller[runtime_zero..].ptr, 0, *align(2) u32); + testIndex(smaller[runtime_zero..].ptr, 1, *align(2) u32); + testIndex(smaller[runtime_zero..].ptr, 2, *align(2) u32); + testIndex(smaller[runtime_zero..].ptr, 3, *align(2) u32); + + // has to use ABI alignment because index known at runtime only + testIndex2(array[runtime_zero..].ptr, 0, *u8); + testIndex2(array[runtime_zero..].ptr, 1, *u8); + testIndex2(array[runtime_zero..].ptr, 2, *u8); + testIndex2(array[runtime_zero..].ptr, 3, *u8); +} +fn testIndex(smaller: [*]align(2) u32, index: usize, comptime T: type) void { + comptime expect(@TypeOf(&smaller[index]) == T); +} +fn testIndex2(ptr: [*]align(4) u8, index: usize, comptime T: type) void { + comptime expect(@TypeOf(&ptr[index]) == T); +} + +test "alignstack" { + expect(fnWithAlignedStack() == 1234); +} + +fn fnWithAlignedStack() i32 { + @setAlignStack(256); + return 1234; +} + +test "alignment of structs" { + expect(@alignOf(struct { + a: i32, + b: *i32, + }) == @alignOf(usize)); +} + +test "alignment of function with c calling convention" { + var runtime_nothing = nothing; + const casted1 = @ptrCast(*const u8, runtime_nothing); + const casted2 = @ptrCast(fn () callconv(.C) void, casted1); + casted2(); +} + +fn nothing() callconv(.C) void {} + +test "return error union with 128-bit integer" { + expect(3 == try give()); +} +fn give() anyerror!u128 { + return 3; +} + +test "alignment of >= 128-bit integer type" { + expect(@alignOf(u128) == 16); + expect(@alignOf(u129) == 16); +} + +test "alignment of struct with 128-bit field" { + expect(@alignOf(struct { + x: u128, + }) == 16); + + comptime { + expect(@alignOf(struct { + x: u128, + }) == 16); + } +} + +test "size of extern struct with 128-bit field" { + expect(@sizeOf(extern struct { + x: u128, + y: u8, + }) == 32); + + comptime { + expect(@sizeOf(extern struct { + x: u128, + y: u8, + }) == 32); + } +} + +const DefaultAligned = struct { + nevermind: u32, + badguy: i128, +}; + +test "read 128-bit field from default aligned struct in stack memory" { + var default_aligned = DefaultAligned{ + .nevermind = 1, + .badguy = 12, + }; + expect((@ptrToInt(&default_aligned.badguy) % 16) == 0); + expect(12 == default_aligned.badguy); +} + +var default_aligned_global = DefaultAligned{ + .nevermind = 1, + .badguy = 12, +}; + +test "read 128-bit field from default aligned struct in global memory" { + expect((@ptrToInt(&default_aligned_global.badguy) % 16) == 0); + expect(12 == default_aligned_global.badguy); +} + +test "struct field explicit alignment" { + const S = struct { + const Node = struct { + next: *Node, + massive_byte: u8 align(64), + }; + }; + + var node: S.Node = undefined; + node.massive_byte = 100; + expect(node.massive_byte == 100); + comptime expect(@TypeOf(&node.massive_byte) == *align(64) u8); + expect(@ptrToInt(&node.massive_byte) % 64 == 0); +} + +test "align(@alignOf(T)) T does not force resolution of T" { + const S = struct { + const A = struct { + a: *align(@alignOf(A)) A, + }; + fn doTheTest() void { + suspend { + resume @frame(); + } + _ = bar(@Frame(doTheTest)); + } + fn bar(comptime T: type) *align(@alignOf(T)) T { + ok = true; + return undefined; + } + + var ok = false; + }; + _ = async S.doTheTest(); + expect(S.ok); +} + +test "align(N) on functions" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + expect((@ptrToInt(overaligned_fn) & (0x1000 - 1)) == 0); +} +fn overaligned_fn() align(0x1000) i32 { + return 42; +} diff --git a/test/behavior/alignof.zig b/test/behavior/alignof.zig new file mode 100644 index 0000000000..d2ead5d2b8 --- /dev/null +++ b/test/behavior/alignof.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const expect = std.testing.expect; +const builtin = @import("builtin"); +const native_arch = builtin.target.cpu.arch; +const maxInt = std.math.maxInt; + +const Foo = struct { + x: u32, + y: u32, + z: u32, +}; + +test "@alignOf(T) before referencing T" { + comptime expect(@alignOf(Foo) != maxInt(usize)); + if (native_arch == .x86_64) { + comptime expect(@alignOf(Foo) == 4); + } +} + +test "comparison of @alignOf(T) against zero" { + { + const T = struct { x: u32 }; + expect(!(@alignOf(T) == 0)); + expect(@alignOf(T) != 0); + expect(!(@alignOf(T) < 0)); + expect(!(@alignOf(T) <= 0)); + expect(@alignOf(T) > 0); + expect(@alignOf(T) >= 0); + } + { + const T = struct {}; + expect(@alignOf(T) == 0); + expect(!(@alignOf(T) != 0)); + expect(!(@alignOf(T) < 0)); + expect(@alignOf(T) <= 0); + expect(!(@alignOf(T) > 0)); + expect(@alignOf(T) >= 0); + } +} diff --git a/test/behavior/array.zig b/test/behavior/array.zig new file mode 100644 index 0000000000..39ccc72960 --- /dev/null +++ b/test/behavior/array.zig @@ -0,0 +1,489 @@ +const std = @import("std"); +const testing = std.testing; +const mem = std.mem; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "arrays" { + var array: [5]u32 = undefined; + + var i: u32 = 0; + while (i < 5) { + array[i] = i + 1; + i = array[i]; + } + + i = 0; + var accumulator = @as(u32, 0); + while (i < 5) { + accumulator += array[i]; + + i += 1; + } + + expect(accumulator == 15); + expect(getArrayLen(&array) == 5); +} +fn getArrayLen(a: []const u32) usize { + return a.len; +} + +test "array with sentinels" { + const S = struct { + fn doTheTest(is_ct: bool) void { + if (is_ct) { + var zero_sized: [0:0xde]u8 = [_:0xde]u8{}; + // Disabled at runtime because of + // https://github.com/ziglang/zig/issues/4372 + expectEqual(@as(u8, 0xde), zero_sized[0]); + var reinterpreted = @ptrCast(*[1]u8, &zero_sized); + expectEqual(@as(u8, 0xde), reinterpreted[0]); + } + var arr: [3:0x55]u8 = undefined; + // Make sure the sentinel pointer is pointing after the last element + if (!is_ct) { + const sentinel_ptr = @ptrToInt(&arr[3]); + const last_elem_ptr = @ptrToInt(&arr[2]); + expectEqual(@as(usize, 1), sentinel_ptr - last_elem_ptr); + } + // Make sure the sentinel is writeable + arr[3] = 0x55; + } + }; + + S.doTheTest(false); + comptime S.doTheTest(true); +} + +test "void arrays" { + var array: [4]void = undefined; + array[0] = void{}; + array[1] = array[2]; + expect(@sizeOf(@TypeOf(array)) == 0); + expect(array.len == 4); +} + +test "array literal" { + const hex_mult = [_]u16{ + 4096, + 256, + 16, + 1, + }; + + expect(hex_mult.len == 4); + expect(hex_mult[1] == 256); +} + +test "array dot len const expr" { + expect(comptime x: { + break :x some_array.len == 4; + }); +} + +const ArrayDotLenConstExpr = struct { + y: [some_array.len]u8, +}; +const some_array = [_]u8{ + 0, + 1, + 2, + 3, +}; + +test "nested arrays" { + const array_of_strings = [_][]const u8{ + "hello", + "this", + "is", + "my", + "thing", + }; + for (array_of_strings) |s, i| { + if (i == 0) expect(mem.eql(u8, s, "hello")); + if (i == 1) expect(mem.eql(u8, s, "this")); + if (i == 2) expect(mem.eql(u8, s, "is")); + if (i == 3) expect(mem.eql(u8, s, "my")); + if (i == 4) expect(mem.eql(u8, s, "thing")); + } +} + +var s_array: [8]Sub = undefined; +const Sub = struct { + b: u8, +}; +const Str = struct { + a: []Sub, +}; +test "set global var array via slice embedded in struct" { + var s = Str{ .a = s_array[0..] }; + + s.a[0].b = 1; + s.a[1].b = 2; + s.a[2].b = 3; + + expect(s_array[0].b == 1); + expect(s_array[1].b == 2); + expect(s_array[2].b == 3); +} + +test "array literal with specified size" { + var array = [2]u8{ + 1, + 2, + }; + expect(array[0] == 1); + expect(array[1] == 2); +} + +test "array len field" { + var arr = [4]u8{ 0, 0, 0, 0 }; + var ptr = &arr; + expect(arr.len == 4); + comptime expect(arr.len == 4); + expect(ptr.len == 4); + comptime expect(ptr.len == 4); +} + +test "single-item pointer to array indexing and slicing" { + testSingleItemPtrArrayIndexSlice(); + comptime testSingleItemPtrArrayIndexSlice(); +} + +fn testSingleItemPtrArrayIndexSlice() void { + { + var array: [4]u8 = "aaaa".*; + doSomeMangling(&array); + expect(mem.eql(u8, "azya", &array)); + } + { + var array = "aaaa".*; + doSomeMangling(&array); + expect(mem.eql(u8, "azya", &array)); + } +} + +fn doSomeMangling(array: *[4]u8) void { + array[1] = 'z'; + array[2..3][0] = 'y'; +} + +test "implicit cast single-item pointer" { + testImplicitCastSingleItemPtr(); + comptime testImplicitCastSingleItemPtr(); +} + +fn testImplicitCastSingleItemPtr() void { + var byte: u8 = 100; + const slice = @as(*[1]u8, &byte)[0..]; + slice[0] += 1; + expect(byte == 101); +} + +fn testArrayByValAtComptime(b: [2]u8) u8 { + return b[0]; +} + +test "comptime evalutating function that takes array by value" { + const arr = [_]u8{ 0, 1 }; + _ = comptime testArrayByValAtComptime(arr); + _ = comptime testArrayByValAtComptime(arr); +} + +test "implicit comptime in array type size" { + var arr: [plusOne(10)]bool = undefined; + expect(arr.len == 11); +} + +fn plusOne(x: u32) u32 { + return x + 1; +} + +test "runtime initialize array elem and then implicit cast to slice" { + var two: i32 = 2; + const x: []const i32 = &[_]i32{two}; + expect(x[0] == 2); +} + +test "array literal as argument to function" { + const S = struct { + fn entry(two: i32) void { + foo(&[_]i32{ + 1, + 2, + 3, + }); + foo(&[_]i32{ + 1, + two, + 3, + }); + foo2(true, &[_]i32{ + 1, + 2, + 3, + }); + foo2(true, &[_]i32{ + 1, + two, + 3, + }); + } + fn foo(x: []const i32) void { + expect(x[0] == 1); + expect(x[1] == 2); + expect(x[2] == 3); + } + fn foo2(trash: bool, x: []const i32) void { + expect(trash); + expect(x[0] == 1); + expect(x[1] == 2); + expect(x[2] == 3); + } + }; + S.entry(2); + comptime S.entry(2); +} + +test "double nested array to const slice cast in array literal" { + const S = struct { + fn entry(two: i32) void { + const cases = [_][]const []const i32{ + &[_][]const i32{&[_]i32{1}}, + &[_][]const i32{&[_]i32{ 2, 3 }}, + &[_][]const i32{ + &[_]i32{4}, + &[_]i32{ 5, 6, 7 }, + }, + }; + check(&cases); + + const cases2 = [_][]const i32{ + &[_]i32{1}, + &[_]i32{ two, 3 }, + }; + expect(cases2.len == 2); + expect(cases2[0].len == 1); + expect(cases2[0][0] == 1); + expect(cases2[1].len == 2); + expect(cases2[1][0] == 2); + expect(cases2[1][1] == 3); + + const cases3 = [_][]const []const i32{ + &[_][]const i32{&[_]i32{1}}, + &[_][]const i32{&[_]i32{ two, 3 }}, + &[_][]const i32{ + &[_]i32{4}, + &[_]i32{ 5, 6, 7 }, + }, + }; + check(&cases3); + } + + fn check(cases: []const []const []const i32) void { + expect(cases.len == 3); + expect(cases[0].len == 1); + expect(cases[0][0].len == 1); + expect(cases[0][0][0] == 1); + expect(cases[1].len == 1); + expect(cases[1][0].len == 2); + expect(cases[1][0][0] == 2); + expect(cases[1][0][1] == 3); + expect(cases[2].len == 2); + expect(cases[2][0].len == 1); + expect(cases[2][0][0] == 4); + expect(cases[2][1].len == 3); + expect(cases[2][1][0] == 5); + expect(cases[2][1][1] == 6); + expect(cases[2][1][2] == 7); + } + }; + S.entry(2); + comptime S.entry(2); +} + +test "read/write through global variable array of struct fields initialized via array mult" { + const S = struct { + fn doTheTest() void { + expect(storage[0].term == 1); + storage[0] = MyStruct{ .term = 123 }; + expect(storage[0].term == 123); + } + + pub const MyStruct = struct { + term: usize, + }; + + var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1; + }; + S.doTheTest(); +} + +test "implicit cast zero sized array ptr to slice" { + { + var b = "".*; + const c: []const u8 = &b; + expect(c.len == 0); + } + { + var b: [0]u8 = "".*; + const c: []const u8 = &b; + expect(c.len == 0); + } +} + +test "anonymous list literal syntax" { + const S = struct { + fn doTheTest() void { + var array: [4]u8 = .{ 1, 2, 3, 4 }; + expect(array[0] == 1); + expect(array[1] == 2); + expect(array[2] == 3); + expect(array[3] == 4); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "anonymous literal in array" { + const S = struct { + const Foo = struct { + a: usize = 2, + b: usize = 4, + }; + fn doTheTest() void { + var array: [2]Foo = .{ + .{ .a = 3 }, + .{ .b = 3 }, + }; + expect(array[0].a == 3); + expect(array[0].b == 4); + expect(array[1].a == 2); + expect(array[1].b == 3); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "access the null element of a null terminated array" { + const S = struct { + fn doTheTest() void { + var array: [4:0]u8 = .{ 'a', 'o', 'e', 'u' }; + expect(array[4] == 0); + var len: usize = 4; + expect(array[len] == 0); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "type deduction for array subscript expression" { + const S = struct { + fn doTheTest() void { + var array = [_]u8{ 0x55, 0xAA }; + var v0 = true; + expectEqual(@as(u8, 0xAA), array[if (v0) 1 else 0]); + var v1 = false; + expectEqual(@as(u8, 0x55), array[if (v1) 1 else 0]); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "sentinel element count towards the ABI size calculation" { + const S = struct { + fn doTheTest() void { + const T = packed struct { + fill_pre: u8 = 0x55, + data: [0:0]u8 = undefined, + fill_post: u8 = 0xAA, + }; + var x = T{}; + var as_slice = mem.asBytes(&x); + expectEqual(@as(usize, 3), as_slice.len); + expectEqual(@as(u8, 0x55), as_slice[0]); + expectEqual(@as(u8, 0xAA), as_slice[2]); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "zero-sized array with recursive type definition" { + const U = struct { + fn foo(comptime T: type, comptime n: usize) type { + return struct { + s: [n]T, + x: usize = n, + }; + } + }; + + const S = struct { + list: U.foo(@This(), 0), + }; + + var t: S = .{ .list = .{ .s = undefined } }; + expectEqual(@as(usize, 0), t.list.x); +} + +test "type coercion of anon struct literal to array" { + const S = struct { + const U = union{ + a: u32, + b: bool, + c: []const u8, + }; + + fn doTheTest() void { + var x1: u8 = 42; + const t1 = .{ x1, 56, 54 }; + var arr1: [3]u8 = t1; + expect(arr1[0] == 42); + expect(arr1[1] == 56); + expect(arr1[2] == 54); + + var x2: U = .{ .a = 42 }; + const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } }; + var arr2: [3]U = t2; + expect(arr2[0].a == 42); + expect(arr2[1].b == true); + expect(mem.eql(u8, arr2[2].c, "hello")); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "type coercion of pointer to anon struct literal to pointer to array" { + const S = struct { + const U = union{ + a: u32, + b: bool, + c: []const u8, + }; + + fn doTheTest() void { + var x1: u8 = 42; + const t1 = &.{ x1, 56, 54 }; + var arr1: *const[3]u8 = t1; + expect(arr1[0] == 42); + expect(arr1[1] == 56); + expect(arr1[2] == 54); + + var x2: U = .{ .a = 42 }; + const t2 = &.{ x2, .{ .b = true }, .{ .c = "hello" } }; + var arr2: *const [3]U = t2; + expect(arr2[0].a == 42); + expect(arr2[1].b == true); + expect(mem.eql(u8, arr2[2].c, "hello")); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/asm.zig b/test/behavior/asm.zig new file mode 100644 index 0000000000..170ad3325d --- /dev/null +++ b/test/behavior/asm.zig @@ -0,0 +1,94 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const is_x86_64_linux = std.Target.current.cpu.arch == .x86_64 and std.Target.current.os.tag == .linux; + +comptime { + if (is_x86_64_linux) { + asm ( + \\.globl this_is_my_alias; + \\.type this_is_my_alias, @function; + \\.set this_is_my_alias, derp; + ); + } +} + +test "module level assembly" { + if (is_x86_64_linux) { + expect(this_is_my_alias() == 1234); + } +} + +test "output constraint modifiers" { + // This is only testing compilation. + var a: u32 = 3; + asm volatile ("" + : [_] "=m,r" (a) + : + : "" + ); + asm volatile ("" + : [_] "=r,m" (a) + : + : "" + ); +} + +test "alternative constraints" { + // Make sure we allow commas as a separator for alternative constraints. + var a: u32 = 3; + asm volatile ("" + : [_] "=r,m" (a) + : [_] "r,m" (a) + : "" + ); +} + +test "sized integer/float in asm input" { + asm volatile ("" + : + : [_] "m" (@as(usize, 3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(i15, -3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(u3, 3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(i3, 3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(u121, 3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(i121, 3)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(f32, 3.17)) + : "" + ); + asm volatile ("" + : + : [_] "m" (@as(f64, 3.17)) + : "" + ); +} + +extern fn this_is_my_alias() i32; + +export fn derp() i32 { + return 1234; +} diff --git a/test/behavior/async_fn.zig b/test/behavior/async_fn.zig new file mode 100644 index 0000000000..0765eac7e8 --- /dev/null +++ b/test/behavior/async_fn.zig @@ -0,0 +1,1673 @@ +const std = @import("std"); +const builtin = std.builtin; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualStrings = std.testing.expectEqualStrings; +const expectError = std.testing.expectError; + +var global_x: i32 = 1; + +test "simple coroutine suspend and resume" { + var frame = async simpleAsyncFn(); + expect(global_x == 2); + resume frame; + expect(global_x == 3); + const af: anyframe->void = &frame; + resume frame; + expect(global_x == 4); +} +fn simpleAsyncFn() void { + global_x += 1; + suspend {} + global_x += 1; + suspend {} + global_x += 1; +} + +var global_y: i32 = 1; + +test "pass parameter to coroutine" { + var p = async simpleAsyncFnWithArg(2); + expect(global_y == 3); + resume p; + expect(global_y == 5); +} +fn simpleAsyncFnWithArg(delta: i32) void { + global_y += delta; + suspend {} + global_y += delta; +} + +test "suspend at end of function" { + const S = struct { + var x: i32 = 1; + + fn doTheTest() void { + expect(x == 1); + const p = async suspendAtEnd(); + expect(x == 2); + } + + fn suspendAtEnd() void { + x += 1; + suspend {} + } + }; + S.doTheTest(); +} + +test "local variable in async function" { + const S = struct { + var x: i32 = 0; + + fn doTheTest() void { + expect(x == 0); + var p = async add(1, 2); + expect(x == 0); + resume p; + expect(x == 0); + resume p; + expect(x == 0); + resume p; + expect(x == 3); + } + + fn add(a: i32, b: i32) void { + var accum: i32 = 0; + suspend {} + accum += a; + suspend {} + accum += b; + suspend {} + x = accum; + } + }; + S.doTheTest(); +} + +test "calling an inferred async function" { + const S = struct { + var x: i32 = 1; + var other_frame: *@Frame(other) = undefined; + + fn doTheTest() void { + _ = async first(); + expect(x == 1); + resume other_frame.*; + expect(x == 2); + } + + fn first() void { + other(); + } + fn other() void { + other_frame = @frame(); + suspend {} + x += 1; + } + }; + S.doTheTest(); +} + +test "@frameSize" { + const S = struct { + fn doTheTest() void { + { + var ptr = @ptrCast(fn (i32) callconv(.Async) void, other); + const size = @frameSize(ptr); + expect(size == @sizeOf(@Frame(other))); + } + { + var ptr = @ptrCast(fn () callconv(.Async) void, first); + const size = @frameSize(ptr); + expect(size == @sizeOf(@Frame(first))); + } + } + + fn first() void { + other(1); + } + fn other(param: i32) void { + var local: i32 = undefined; + suspend {} + } + }; + S.doTheTest(); +} + +test "coroutine suspend, resume" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async amain(); + seq('d'); + resume frame; + seq('h'); + + expect(std.mem.eql(u8, &points, "abcdefgh")); + } + + fn amain() void { + seq('a'); + var f = async testAsyncSeq(); + seq('c'); + await f; + seq('g'); + } + + fn testAsyncSeq() void { + defer seq('f'); + + seq('b'); + suspend { + frame = @frame(); + } + seq('e'); + } + var points = [_]u8{'x'} ** "abcdefgh".len; + var index: usize = 0; + + fn seq(c: u8) void { + points[index] = c; + index += 1; + } + }; + S.doTheTest(); +} + +test "coroutine suspend with block" { + const p = async testSuspendBlock(); + expect(!global_result); + resume a_promise; + expect(global_result); +} + +var a_promise: anyframe = undefined; +var global_result = false; +fn testSuspendBlock() callconv(.Async) void { + suspend { + comptime expect(@TypeOf(@frame()) == *@Frame(testSuspendBlock)); + a_promise = @frame(); + } + + // Test to make sure that @frame() works as advertised (issue #1296) + // var our_handle: anyframe = @frame(); + expect(a_promise == @as(anyframe, @frame())); + + global_result = true; +} + +var await_a_promise: anyframe = undefined; +var await_final_result: i32 = 0; + +test "coroutine await" { + await_seq('a'); + var p = async await_amain(); + await_seq('f'); + resume await_a_promise; + await_seq('i'); + expect(await_final_result == 1234); + expect(std.mem.eql(u8, &await_points, "abcdefghi")); +} +fn await_amain() callconv(.Async) void { + await_seq('b'); + var p = async await_another(); + await_seq('e'); + await_final_result = await p; + await_seq('h'); +} +fn await_another() callconv(.Async) i32 { + await_seq('c'); + suspend { + await_seq('d'); + await_a_promise = @frame(); + } + await_seq('g'); + return 1234; +} + +var await_points = [_]u8{0} ** "abcdefghi".len; +var await_seq_index: usize = 0; + +fn await_seq(c: u8) void { + await_points[await_seq_index] = c; + await_seq_index += 1; +} + +var early_final_result: i32 = 0; + +test "coroutine await early return" { + early_seq('a'); + var p = async early_amain(); + early_seq('f'); + expect(early_final_result == 1234); + expect(std.mem.eql(u8, &early_points, "abcdef")); +} +fn early_amain() callconv(.Async) void { + early_seq('b'); + var p = async early_another(); + early_seq('d'); + early_final_result = await p; + early_seq('e'); +} +fn early_another() callconv(.Async) i32 { + early_seq('c'); + return 1234; +} + +var early_points = [_]u8{0} ** "abcdef".len; +var early_seq_index: usize = 0; + +fn early_seq(c: u8) void { + early_points[early_seq_index] = c; + early_seq_index += 1; +} + +test "async function with dot syntax" { + const S = struct { + var y: i32 = 1; + fn foo() callconv(.Async) void { + y += 1; + suspend {} + } + }; + const p = async S.foo(); + expect(S.y == 2); +} + +test "async fn pointer in a struct field" { + var data: i32 = 1; + const Foo = struct { + bar: fn (*i32) callconv(.Async) void, + }; + var foo = Foo{ .bar = simpleAsyncFn2 }; + var bytes: [64]u8 align(16) = undefined; + const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); + comptime expect(@TypeOf(f) == anyframe->void); + expect(data == 2); + resume f; + expect(data == 4); + _ = async doTheAwait(f); + expect(data == 4); +} + +fn doTheAwait(f: anyframe->void) void { + await f; +} +fn simpleAsyncFn2(y: *i32) callconv(.Async) void { + defer y.* += 2; + y.* += 1; + suspend {} +} + +test "@asyncCall with return type" { + const Foo = struct { + bar: fn () callconv(.Async) i32, + + var global_frame: anyframe = undefined; + fn middle() callconv(.Async) i32 { + return afunc(); + } + + fn afunc() i32 { + global_frame = @frame(); + suspend {} + return 1234; + } + }; + var foo = Foo{ .bar = Foo.middle }; + var bytes: [150]u8 align(16) = undefined; + var aresult: i32 = 0; + _ = @asyncCall(&bytes, &aresult, foo.bar, .{}); + expect(aresult == 0); + resume Foo.global_frame; + expect(aresult == 1234); +} + +test "async fn with inferred error set" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var frame: [1]@Frame(middle) = undefined; + var fn_ptr = middle; + var result: @typeInfo(@typeInfo(@TypeOf(fn_ptr)).Fn.return_type.?).ErrorUnion.error_set!void = undefined; + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr, .{}); + resume global_frame; + std.testing.expectError(error.Fail, result); + } + fn middle() callconv(.Async) !void { + var f = async middle2(); + return await f; + } + + fn middle2() !void { + return failing(); + } + + fn failing() !void { + global_frame = @frame(); + suspend {} + return error.Fail; + } + }; + S.doTheTest(); +} + +test "error return trace across suspend points - early return" { + const p = nonFailing(); + resume p; + const p2 = async printTrace(p); +} + +test "error return trace across suspend points - async return" { + const p = nonFailing(); + const p2 = async printTrace(p); + resume p; +} + +fn nonFailing() (anyframe->anyerror!void) { + const Static = struct { + var frame: @Frame(suspendThenFail) = undefined; + }; + Static.frame = async suspendThenFail(); + return &Static.frame; +} +fn suspendThenFail() callconv(.Async) anyerror!void { + suspend {} + return error.Fail; +} +fn printTrace(p: anyframe->(anyerror!void)) callconv(.Async) void { + (await p) catch |e| { + std.testing.expect(e == error.Fail); + if (@errorReturnTrace()) |trace| { + expect(trace.index == 1); + } else switch (builtin.mode) { + .Debug, .ReleaseSafe => @panic("expected return trace"), + .ReleaseFast, .ReleaseSmall => {}, + } + }; +} + +test "break from suspend" { + var my_result: i32 = 1; + const p = async testBreakFromSuspend(&my_result); + std.testing.expect(my_result == 2); +} +fn testBreakFromSuspend(my_result: *i32) callconv(.Async) void { + suspend { + resume @frame(); + } + my_result.* += 1; + suspend {} + my_result.* += 1; +} + +test "heap allocated async function frame" { + const S = struct { + var x: i32 = 42; + + fn doTheTest() !void { + const frame = try std.testing.allocator.create(@Frame(someFunc)); + defer std.testing.allocator.destroy(frame); + + expect(x == 42); + frame.* = async someFunc(); + expect(x == 43); + resume frame; + expect(x == 44); + } + + fn someFunc() void { + x += 1; + suspend {} + x += 1; + } + }; + try S.doTheTest(); +} + +test "async function call return value" { + const S = struct { + var frame: anyframe = undefined; + var pt = Point{ .x = 10, .y = 11 }; + + fn doTheTest() void { + expectEqual(pt.x, 10); + expectEqual(pt.y, 11); + _ = async first(); + expectEqual(pt.x, 10); + expectEqual(pt.y, 11); + resume frame; + expectEqual(pt.x, 1); + expectEqual(pt.y, 2); + } + + fn first() void { + pt = second(1, 2); + } + + fn second(x: i32, y: i32) Point { + return other(x, y); + } + + fn other(x: i32, y: i32) Point { + frame = @frame(); + suspend {} + return Point{ + .x = x, + .y = y, + }; + } + + const Point = struct { + x: i32, + y: i32, + }; + }; + S.doTheTest(); +} + +test "suspension points inside branching control flow" { + const S = struct { + var result: i32 = 10; + + fn doTheTest() void { + expect(10 == result); + var frame = async func(true); + expect(10 == result); + resume frame; + expect(11 == result); + resume frame; + expect(12 == result); + resume frame; + expect(13 == result); + } + + fn func(b: bool) void { + while (b) { + suspend {} + result += 1; + } + } + }; + S.doTheTest(); +} + +test "call async function which has struct return type" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async atest(); + resume frame; + } + + fn atest() void { + const result = func(); + expect(result.x == 5); + expect(result.y == 6); + } + + const Point = struct { + x: usize, + y: usize, + }; + + fn func() Point { + suspend { + frame = @frame(); + } + return Point{ + .x = 5, + .y = 6, + }; + } + }; + S.doTheTest(); +} + +test "pass string literal to async function" { + const S = struct { + var frame: anyframe = undefined; + var ok: bool = false; + + fn doTheTest() void { + _ = async hello("hello"); + resume frame; + expect(ok); + } + + fn hello(msg: []const u8) void { + frame = @frame(); + suspend {} + expectEqualStrings("hello", msg); + ok = true; + } + }; + S.doTheTest(); +} + +test "await inside an errdefer" { + const S = struct { + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async amainWrap(); + resume frame; + } + + fn amainWrap() !void { + var foo = async func(); + errdefer await foo; + return error.Bad; + } + + fn func() void { + frame = @frame(); + suspend {} + } + }; + S.doTheTest(); +} + +test "try in an async function with error union and non-zero-bit payload" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + _ = async amain(); + resume frame; + expect(ok); + } + + fn amain() void { + std.testing.expectError(error.Bad, theProblem()); + ok = true; + } + + fn theProblem() ![]u8 { + frame = @frame(); + suspend {} + const result = try other(); + return result; + } + + fn other() ![]u8 { + return error.Bad; + } + }; + S.doTheTest(); +} + +test "returning a const error from async function" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + _ = async amain(); + resume frame; + expect(ok); + } + + fn amain() !void { + var download_frame = async fetchUrl(10, "a string"); + const download_text = try await download_frame; + + @panic("should not get here"); + } + + fn fetchUrl(unused: i32, url: []const u8) ![]u8 { + frame = @frame(); + suspend {} + ok = true; + return error.OutOfMemory; + } + }; + S.doTheTest(); +} + +test "async/await typical usage" { + inline for ([_]bool{ false, true }) |b1| { + inline for ([_]bool{ false, true }) |b2| { + inline for ([_]bool{ false, true }) |b3| { + inline for ([_]bool{ false, true }) |b4| { + testAsyncAwaitTypicalUsage(b1, b2, b3, b4).doTheTest(); + } + } + } + } +} + +fn testAsyncAwaitTypicalUsage( + comptime simulate_fail_download: bool, + comptime simulate_fail_file: bool, + comptime suspend_download: bool, + comptime suspend_file: bool, +) type { + return struct { + fn doTheTest() void { + _ = async amainWrap(); + if (suspend_file) { + resume global_file_frame; + } + if (suspend_download) { + resume global_download_frame; + } + } + fn amainWrap() void { + if (amain()) |_| { + expect(!simulate_fail_download); + expect(!simulate_fail_file); + } else |e| switch (e) { + error.NoResponse => expect(simulate_fail_download), + error.FileNotFound => expect(simulate_fail_file), + else => @panic("test failure"), + } + } + + fn amain() !void { + const allocator = std.testing.allocator; + var download_frame = async fetchUrl(allocator, "https://example.com/"); + var download_awaited = false; + errdefer if (!download_awaited) { + if (await download_frame) |x| allocator.free(x) else |_| {} + }; + + var file_frame = async readFile(allocator, "something.txt"); + var file_awaited = false; + errdefer if (!file_awaited) { + if (await file_frame) |x| allocator.free(x) else |_| {} + }; + + download_awaited = true; + const download_text = try await download_frame; + defer allocator.free(download_text); + + file_awaited = true; + const file_text = try await file_frame; + defer allocator.free(file_text); + + expect(std.mem.eql(u8, "expected download text", download_text)); + expect(std.mem.eql(u8, "expected file text", file_text)); + } + + var global_download_frame: anyframe = undefined; + fn fetchUrl(allocator: *std.mem.Allocator, url: []const u8) anyerror![]u8 { + const result = try std.mem.dupe(allocator, u8, "expected download text"); + errdefer allocator.free(result); + if (suspend_download) { + suspend { + global_download_frame = @frame(); + } + } + if (simulate_fail_download) return error.NoResponse; + return result; + } + + var global_file_frame: anyframe = undefined; + fn readFile(allocator: *std.mem.Allocator, filename: []const u8) anyerror![]u8 { + const result = try std.mem.dupe(allocator, u8, "expected file text"); + errdefer allocator.free(result); + if (suspend_file) { + suspend { + global_file_frame = @frame(); + } + } + if (simulate_fail_file) return error.FileNotFound; + return result; + } + }; +} + +test "alignment of local variables in async functions" { + const S = struct { + fn doTheTest() void { + var y: u8 = 123; + var x: u8 align(128) = 1; + expect(@ptrToInt(&x) % 128 == 0); + } + }; + S.doTheTest(); +} + +test "no reason to resolve frame still works" { + _ = async simpleNothing(); +} +fn simpleNothing() void { + var x: i32 = 1234; +} + +test "async call a generic function" { + const S = struct { + fn doTheTest() void { + var f = async func(i32, 2); + const result = await f; + expect(result == 3); + } + + fn func(comptime T: type, inc: T) T { + var x: T = 1; + suspend { + resume @frame(); + } + x += inc; + return x; + } + }; + _ = async S.doTheTest(); +} + +test "return from suspend block" { + const S = struct { + fn doTheTest() void { + expect(func() == 1234); + } + fn func() i32 { + suspend { + return 1234; + } + } + }; + _ = async S.doTheTest(); +} + +test "struct parameter to async function is copied to the frame" { + const S = struct { + const Point = struct { + x: i32, + y: i32, + }; + + var frame: anyframe = undefined; + + fn doTheTest() void { + _ = async atest(); + resume frame; + } + + fn atest() void { + var f: @Frame(foo) = undefined; + bar(&f); + clobberStack(10); + } + + fn clobberStack(x: i32) void { + if (x == 0) return; + clobberStack(x - 1); + var y: i32 = x; + } + + fn bar(f: *@Frame(foo)) void { + var pt = Point{ .x = 1, .y = 2 }; + f.* = async foo(pt); + var result = await f; + expect(result == 1); + } + + fn foo(point: Point) i32 { + suspend { + frame = @frame(); + } + return point.x; + } + }; + S.doTheTest(); +} + +test "cast fn to async fn when it is inferred to be async" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + var ptr: fn () callconv(.Async) i32 = undefined; + ptr = func; + var buf: [100]u8 align(16) = undefined; + var result: i32 = undefined; + const f = @asyncCall(&buf, &result, ptr, .{}); + _ = await f; + expect(result == 1234); + ok = true; + } + + fn func() i32 { + suspend { + frame = @frame(); + } + return 1234; + } + }; + _ = async S.doTheTest(); + resume S.frame; + expect(S.ok); +} + +test "cast fn to async fn when it is inferred to be async, awaited directly" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + var ptr: fn () callconv(.Async) i32 = undefined; + ptr = func; + var buf: [100]u8 align(16) = undefined; + var result: i32 = undefined; + _ = await @asyncCall(&buf, &result, ptr, .{}); + expect(result == 1234); + ok = true; + } + + fn func() i32 { + suspend { + frame = @frame(); + } + return 1234; + } + }; + _ = async S.doTheTest(); + resume S.frame; + expect(S.ok); +} + +test "await does not force async if callee is blocking" { + const S = struct { + fn simple() i32 { + return 1234; + } + }; + var x = async S.simple(); + expect(await x == 1234); +} + +test "recursive async function" { + expect(recursiveAsyncFunctionTest(false).doTheTest() == 55); + expect(recursiveAsyncFunctionTest(true).doTheTest() == 55); +} + +fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type { + return struct { + fn fib(allocator: *std.mem.Allocator, x: u32) error{OutOfMemory}!u32 { + if (x <= 1) return x; + + if (suspending_implementation) { + suspend { + resume @frame(); + } + } + + const f1 = try allocator.create(@Frame(fib)); + defer allocator.destroy(f1); + + const f2 = try allocator.create(@Frame(fib)); + defer allocator.destroy(f2); + + f1.* = async fib(allocator, x - 1); + var f1_awaited = false; + errdefer if (!f1_awaited) { + _ = await f1; + }; + + f2.* = async fib(allocator, x - 2); + var f2_awaited = false; + errdefer if (!f2_awaited) { + _ = await f2; + }; + + var sum: u32 = 0; + + f1_awaited = true; + sum += try await f1; + + f2_awaited = true; + sum += try await f2; + + return sum; + } + + fn doTheTest() u32 { + if (suspending_implementation) { + var result: u32 = undefined; + _ = async amain(&result); + return result; + } else { + return fib(std.testing.allocator, 10) catch unreachable; + } + } + + fn amain(result: *u32) void { + var x = async fib(std.testing.allocator, 10); + result.* = (await x) catch unreachable; + } + }; +} + +test "@asyncCall with comptime-known function, but not awaited directly" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var frame: [1]@Frame(middle) = undefined; + var result: @typeInfo(@typeInfo(@TypeOf(middle)).Fn.return_type.?).ErrorUnion.error_set!void = undefined; + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle, .{}); + resume global_frame; + std.testing.expectError(error.Fail, result); + } + fn middle() callconv(.Async) !void { + var f = async middle2(); + return await f; + } + + fn middle2() !void { + return failing(); + } + + fn failing() !void { + global_frame = @frame(); + suspend {} + return error.Fail; + } + }; + S.doTheTest(); +} + +test "@asyncCall with actual frame instead of byte buffer" { + const S = struct { + fn func() i32 { + suspend {} + return 1234; + } + }; + var frame: @Frame(S.func) = undefined; + var result: i32 = undefined; + const ptr = @asyncCall(&frame, &result, S.func, .{}); + resume ptr; + expect(result == 1234); +} + +test "@asyncCall using the result location inside the frame" { + const S = struct { + fn simple2(y: *i32) callconv(.Async) i32 { + defer y.* += 2; + y.* += 1; + suspend {} + return 1234; + } + fn getAnswer(f: anyframe->i32, out: *i32) void { + out.* = await f; + } + }; + var data: i32 = 1; + const Foo = struct { + bar: fn (*i32) callconv(.Async) i32, + }; + var foo = Foo{ .bar = S.simple2 }; + var bytes: [64]u8 align(16) = undefined; + const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); + comptime expect(@TypeOf(f) == anyframe->i32); + expect(data == 2); + resume f; + expect(data == 4); + _ = async S.getAnswer(f, &data); + expect(data == 1234); +} + +test "@TypeOf an async function call of generic fn with error union type" { + const S = struct { + fn func(comptime x: anytype) anyerror!i32 { + const T = @TypeOf(async func(x)); + comptime expect(T == @typeInfo(@TypeOf(@frame())).Pointer.child); + return undefined; + } + }; + _ = async S.func(i32); +} + +test "using @TypeOf on a generic function call" { + const S = struct { + var global_frame: anyframe = undefined; + var global_ok = false; + + var buf: [100]u8 align(16) = undefined; + + fn amain(x: anytype) void { + if (x == 0) { + global_ok = true; + return; + } + suspend { + global_frame = @frame(); + } + const F = @TypeOf(async amain(x - 1)); + const frame = @intToPtr(*F, @ptrToInt(&buf)); + return await @asyncCall(frame, {}, amain, .{x - 1}); + } + }; + _ = async S.amain(@as(u32, 1)); + resume S.global_frame; + expect(S.global_ok); +} + +test "recursive call of await @asyncCall with struct return type" { + const S = struct { + var global_frame: anyframe = undefined; + var global_ok = false; + + var buf: [100]u8 align(16) = undefined; + + fn amain(x: anytype) Foo { + if (x == 0) { + global_ok = true; + return Foo{ .x = 1, .y = 2, .z = 3 }; + } + suspend { + global_frame = @frame(); + } + const F = @TypeOf(async amain(x - 1)); + const frame = @intToPtr(*F, @ptrToInt(&buf)); + return await @asyncCall(frame, {}, amain, .{x - 1}); + } + + const Foo = struct { + x: u64, + y: u64, + z: u64, + }; + }; + var res: S.Foo = undefined; + var frame: @TypeOf(async S.amain(@as(u32, 1))) = undefined; + _ = @asyncCall(&frame, &res, S.amain, .{@as(u32, 1)}); + resume S.global_frame; + expect(S.global_ok); + expect(res.x == 1); + expect(res.y == 2); + expect(res.z == 3); +} + +test "nosuspend function call" { + const S = struct { + fn doTheTest() void { + const result = nosuspend add(50, 100); + expect(result == 150); + } + fn add(a: i32, b: i32) i32 { + if (a > 100) { + suspend {} + } + return a + b; + } + }; + S.doTheTest(); +} + +test "await used in expression and awaiting fn with no suspend but async calling convention" { + const S = struct { + fn atest() void { + var f1 = async add(1, 2); + var f2 = async add(3, 4); + + const sum = (await f1) + (await f2); + expect(sum == 10); + } + fn add(a: i32, b: i32) callconv(.Async) i32 { + return a + b; + } + }; + _ = async S.atest(); +} + +test "await used in expression after a fn call" { + const S = struct { + fn atest() void { + var f1 = async add(3, 4); + var sum: i32 = 0; + sum = foo() + await f1; + expect(sum == 8); + } + fn add(a: i32, b: i32) callconv(.Async) i32 { + return a + b; + } + fn foo() i32 { + return 1; + } + }; + _ = async S.atest(); +} + +test "async fn call used in expression after a fn call" { + const S = struct { + fn atest() void { + var sum: i32 = 0; + sum = foo() + add(3, 4); + expect(sum == 8); + } + fn add(a: i32, b: i32) callconv(.Async) i32 { + return a + b; + } + fn foo() i32 { + return 1; + } + }; + _ = async S.atest(); +} + +test "suspend in for loop" { + const S = struct { + var global_frame: ?anyframe = null; + + fn doTheTest() void { + _ = async atest(); + while (global_frame) |f| resume f; + } + + fn atest() void { + expect(func(&[_]u8{ 1, 2, 3 }) == 6); + } + fn func(stuff: []const u8) u32 { + global_frame = @frame(); + var sum: u32 = 0; + for (stuff) |x| { + suspend {} + sum += x; + } + global_frame = null; + return sum; + } + }; + S.doTheTest(); +} + +test "suspend in while loop" { + const S = struct { + var global_frame: ?anyframe = null; + + fn doTheTest() void { + _ = async atest(); + while (global_frame) |f| resume f; + } + + fn atest() void { + expect(optional(6) == 6); + expect(errunion(6) == 6); + } + fn optional(stuff: ?u32) u32 { + global_frame = @frame(); + defer global_frame = null; + while (stuff) |val| { + suspend {} + return val; + } + return 0; + } + fn errunion(stuff: anyerror!u32) u32 { + global_frame = @frame(); + defer global_frame = null; + while (stuff) |val| { + suspend {} + return val; + } else |err| { + return 0; + } + } + }; + S.doTheTest(); +} + +test "correctly spill when returning the error union result of another async fn" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + expect((atest() catch unreachable) == 1234); + } + + fn atest() !i32 { + return fallible1(); + } + + fn fallible1() anyerror!i32 { + suspend { + global_frame = @frame(); + } + return 1234; + } + }; + _ = async S.doTheTest(); + resume S.global_frame; +} + +test "spill target expr in a for loop" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var foo = Foo{ + .slice = &[_]i32{ 1, 2 }, + }; + expect(atest(&foo) == 3); + } + + const Foo = struct { + slice: []const i32, + }; + + fn atest(foo: *Foo) i32 { + var sum: i32 = 0; + for (foo.slice) |x| { + suspend { + global_frame = @frame(); + } + sum += x; + } + return sum; + } + }; + _ = async S.doTheTest(); + resume S.global_frame; + resume S.global_frame; +} + +test "spill target expr in a for loop, with a var decl in the loop body" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var foo = Foo{ + .slice = &[_]i32{ 1, 2 }, + }; + expect(atest(&foo) == 3); + } + + const Foo = struct { + slice: []const i32, + }; + + fn atest(foo: *Foo) i32 { + var sum: i32 = 0; + for (foo.slice) |x| { + // Previously this var decl would prevent spills. This test makes sure + // the for loop spills still happen even though there is a VarDecl in scope + // before the suspend. + var anything = true; + _ = anything; + suspend { + global_frame = @frame(); + } + sum += x; + } + return sum; + } + }; + _ = async S.doTheTest(); + resume S.global_frame; + resume S.global_frame; +} + +test "async call with @call" { + const S = struct { + var global_frame: anyframe = undefined; + fn doTheTest() void { + _ = @call(.{ .modifier = .async_kw }, atest, .{}); + resume global_frame; + } + fn atest() void { + var frame = @call(.{ .modifier = .async_kw }, afoo, .{}); + const res = await frame; + expect(res == 42); + } + fn afoo() i32 { + suspend { + global_frame = @frame(); + } + return 42; + } + }; + S.doTheTest(); +} + +test "async function passed 0-bit arg after non-0-bit arg" { + const S = struct { + var global_frame: anyframe = undefined; + var global_int: i32 = 0; + + fn foo() void { + bar(1, .{}) catch unreachable; + } + + fn bar(x: i32, args: anytype) anyerror!void { + global_frame = @frame(); + suspend {} + global_int = x; + } + }; + _ = async S.foo(); + resume S.global_frame; + expect(S.global_int == 1); +} + +test "async function passed align(16) arg after align(8) arg" { + const S = struct { + var global_frame: anyframe = undefined; + var global_int: u128 = 0; + + fn foo() void { + var a: u128 = 99; + bar(10, .{a}) catch unreachable; + } + + fn bar(x: u64, args: anytype) anyerror!void { + expect(x == 10); + global_frame = @frame(); + suspend {} + global_int = args[0]; + } + }; + _ = async S.foo(); + resume S.global_frame; + expect(S.global_int == 99); +} + +test "async function call resolves target fn frame, comptime func" { + const S = struct { + var global_frame: anyframe = undefined; + var global_int: i32 = 9; + + fn foo() anyerror!void { + const stack_size = 1000; + var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; + return await @asyncCall(&stack_frame, {}, bar, .{}); + } + + fn bar() anyerror!void { + global_frame = @frame(); + suspend {} + global_int += 1; + } + }; + _ = async S.foo(); + resume S.global_frame; + expect(S.global_int == 10); +} + +test "async function call resolves target fn frame, runtime func" { + const S = struct { + var global_frame: anyframe = undefined; + var global_int: i32 = 9; + + fn foo() anyerror!void { + const stack_size = 1000; + var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; + var func: fn () callconv(.Async) anyerror!void = bar; + return await @asyncCall(&stack_frame, {}, func, .{}); + } + + fn bar() anyerror!void { + global_frame = @frame(); + suspend {} + global_int += 1; + } + }; + _ = async S.foo(); + resume S.global_frame; + expect(S.global_int == 10); +} + +test "properly spill optional payload capture value" { + const S = struct { + var global_frame: anyframe = undefined; + var global_int: usize = 2; + + fn foo() void { + var opt: ?usize = 1234; + if (opt) |x| { + bar(); + global_int += x; + } + } + + fn bar() void { + global_frame = @frame(); + suspend {} + global_int += 1; + } + }; + _ = async S.foo(); + resume S.global_frame; + expect(S.global_int == 1237); +} + +test "handle defer interfering with return value spill" { + const S = struct { + var global_frame1: anyframe = undefined; + var global_frame2: anyframe = undefined; + var finished = false; + var baz_happened = false; + + fn doTheTest() void { + _ = async testFoo(); + resume global_frame1; + resume global_frame2; + expect(baz_happened); + expect(finished); + } + + fn testFoo() void { + expectError(error.Bad, foo()); + finished = true; + } + + fn foo() anyerror!void { + defer baz(); + return bar() catch |err| return err; + } + + fn bar() anyerror!void { + global_frame1 = @frame(); + suspend {} + return error.Bad; + } + + fn baz() void { + global_frame2 = @frame(); + suspend {} + baz_happened = true; + } + }; + S.doTheTest(); +} + +test "take address of temporary async frame" { + const S = struct { + var global_frame: anyframe = undefined; + var finished = false; + + fn doTheTest() void { + _ = async asyncDoTheTest(); + resume global_frame; + expect(finished); + } + + fn asyncDoTheTest() void { + expect(finishIt(&async foo(10)) == 1245); + finished = true; + } + + fn foo(arg: i32) i32 { + global_frame = @frame(); + suspend {} + return arg + 1234; + } + + fn finishIt(frame: anyframe->i32) i32 { + return (await frame) + 1; + } + }; + S.doTheTest(); +} + +test "nosuspend await" { + const S = struct { + var finished = false; + + fn doTheTest() void { + var frame = async foo(false); + expect(nosuspend await frame == 42); + finished = true; + } + + fn foo(want_suspend: bool) i32 { + if (want_suspend) { + suspend {} + } + return 42; + } + }; + S.doTheTest(); + expect(S.finished); +} + +test "nosuspend on function calls" { + const S0 = struct { + b: i32 = 42, + }; + const S1 = struct { + fn c() S0 { + return S0{}; + } + fn d() !S0 { + return S0{}; + } + }; + expectEqual(@as(i32, 42), nosuspend S1.c().b); + expectEqual(@as(i32, 42), (try nosuspend S1.d()).b); +} + +test "nosuspend on async function calls" { + const S0 = struct { + b: i32 = 42, + }; + const S1 = struct { + fn c() S0 { + return S0{}; + } + fn d() !S0 { + return S0{}; + } + }; + var frame_c = nosuspend async S1.c(); + expectEqual(@as(i32, 42), (await frame_c).b); + var frame_d = nosuspend async S1.d(); + expectEqual(@as(i32, 42), (try await frame_d).b); +} + +// test "resume nosuspend async function calls" { +// const S0 = struct { +// b: i32 = 42, +// }; +// const S1 = struct { +// fn c() S0 { +// suspend {} +// return S0{}; +// } +// fn d() !S0 { +// suspend {} +// return S0{}; +// } +// }; +// var frame_c = nosuspend async S1.c(); +// resume frame_c; +// expectEqual(@as(i32, 42), (await frame_c).b); +// var frame_d = nosuspend async S1.d(); +// resume frame_d; +// expectEqual(@as(i32, 42), (try await frame_d).b); +// } + +test "nosuspend resume async function calls" { + const S0 = struct { + b: i32 = 42, + }; + const S1 = struct { + fn c() S0 { + suspend {} + return S0{}; + } + fn d() !S0 { + suspend {} + return S0{}; + } + }; + var frame_c = async S1.c(); + nosuspend resume frame_c; + expectEqual(@as(i32, 42), (await frame_c).b); + var frame_d = async S1.d(); + nosuspend resume frame_d; + expectEqual(@as(i32, 42), (try await frame_d).b); +} + +test "avoid forcing frame alignment resolution implicit cast to *c_void" { + const S = struct { + var x: ?*c_void = null; + + fn foo() bool { + suspend { + x = @frame(); + } + return true; + } + }; + var frame = async S.foo(); + resume @ptrCast(anyframe->bool, @alignCast(@alignOf(@Frame(S.foo)), S.x)); + expect(nosuspend await frame); +} + +test "@asyncCall with pass-by-value arguments" { + const F0: u64 = 0xbeefbeefbeefbeef; + const F1: u64 = 0xf00df00df00df00d; + const F2: u64 = 0xcafecafecafecafe; + + const S = struct { + pub const ST = struct { f0: usize, f1: usize }; + pub const AT = [5]u8; + + pub fn f(_fill0: u64, s: ST, _fill1: u64, a: AT, _fill2: u64) callconv(.Async) void { + // Check that the array and struct arguments passed by value don't + // end up overflowing the adjacent fields in the frame structure. + expectEqual(F0, _fill0); + expectEqual(F1, _fill1); + expectEqual(F2, _fill2); + } + }; + + var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined; + // The function pointer must not be comptime-known. + var t = S.f; + var frame_ptr = @asyncCall(&buffer, {}, t, .{ + F0, + .{ .f0 = 1, .f1 = 2 }, + F1, + [_]u8{ 1, 2, 3, 4, 5 }, + F2, + }); +} + +test "@asyncCall with arguments having non-standard alignment" { + const F0: u64 = 0xbeefbeef; + const F1: u64 = 0xf00df00df00df00d; + + const S = struct { + pub fn f(_fill0: u32, s: struct { x: u64 align(16) }, _fill1: u64) callconv(.Async) void { + // The compiler inserts extra alignment for s, check that the + // generated code picks the right slot for fill1. + expectEqual(F0, _fill0); + expectEqual(F1, _fill1); + } + }; + + var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined; + // The function pointer must not be comptime-known. + var t = S.f; + var frame_ptr = @asyncCall(&buffer, {}, t, .{ F0, undefined, F1 }); +} diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig new file mode 100644 index 0000000000..b1fd2b3001 --- /dev/null +++ b/test/behavior/atomics.zig @@ -0,0 +1,221 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const builtin = @import("builtin"); + +test "cmpxchg" { + testCmpxchg(); + comptime testCmpxchg(); +} + +fn testCmpxchg() void { + var x: i32 = 1234; + if (@cmpxchgWeak(i32, &x, 99, 5678, .SeqCst, .SeqCst)) |x1| { + expect(x1 == 1234); + } else { + @panic("cmpxchg should have failed"); + } + + while (@cmpxchgWeak(i32, &x, 1234, 5678, .SeqCst, .SeqCst)) |x1| { + expect(x1 == 1234); + } + expect(x == 5678); + + expect(@cmpxchgStrong(i32, &x, 5678, 42, .SeqCst, .SeqCst) == null); + expect(x == 42); +} + +test "fence" { + var x: i32 = 1234; + @fence(.SeqCst); + x = 5678; +} + +test "atomicrmw and atomicload" { + var data: u8 = 200; + testAtomicRmw(&data); + expect(data == 42); + testAtomicLoad(&data); +} + +fn testAtomicRmw(ptr: *u8) void { + const prev_value = @atomicRmw(u8, ptr, .Xchg, 42, .SeqCst); + expect(prev_value == 200); + comptime { + var x: i32 = 1234; + const y: i32 = 12345; + expect(@atomicLoad(i32, &x, .SeqCst) == 1234); + expect(@atomicLoad(i32, &y, .SeqCst) == 12345); + } +} + +fn testAtomicLoad(ptr: *u8) void { + const x = @atomicLoad(u8, ptr, .SeqCst); + expect(x == 42); +} + +test "cmpxchg with ptr" { + var data1: i32 = 1234; + var data2: i32 = 5678; + var data3: i32 = 9101; + var x: *i32 = &data1; + if (@cmpxchgWeak(*i32, &x, &data2, &data3, .SeqCst, .SeqCst)) |x1| { + expect(x1 == &data1); + } else { + @panic("cmpxchg should have failed"); + } + + while (@cmpxchgWeak(*i32, &x, &data1, &data3, .SeqCst, .SeqCst)) |x1| { + expect(x1 == &data1); + } + expect(x == &data3); + + expect(@cmpxchgStrong(*i32, &x, &data3, &data2, .SeqCst, .SeqCst) == null); + expect(x == &data2); +} + +// TODO this test is disabled until this issue is resolved: +// https://github.com/ziglang/zig/issues/2883 +// otherwise cross compiling will result in: +// lld: error: undefined symbol: __sync_val_compare_and_swap_16 +//test "128-bit cmpxchg" { +// var x: u128 align(16) = 1234; // TODO: https://github.com/ziglang/zig/issues/2987 +// if (@cmpxchgWeak(u128, &x, 99, 5678, .SeqCst, .SeqCst)) |x1| { +// expect(x1 == 1234); +// } else { +// @panic("cmpxchg should have failed"); +// } +// +// while (@cmpxchgWeak(u128, &x, 1234, 5678, .SeqCst, .SeqCst)) |x1| { +// expect(x1 == 1234); +// } +// expect(x == 5678); +// +// expect(@cmpxchgStrong(u128, &x, 5678, 42, .SeqCst, .SeqCst) == null); +// expect(x == 42); +//} + +test "cmpxchg with ignored result" { + var x: i32 = 1234; + var ptr = &x; + + _ = @cmpxchgStrong(i32, &x, 1234, 5678, .Monotonic, .Monotonic); + + expectEqual(@as(i32, 5678), x); +} + +var a_global_variable = @as(u32, 1234); + +test "cmpxchg on a global variable" { + _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); + expectEqual(@as(u32, 42), a_global_variable); +} + +test "atomic load and rmw with enum" { + const Value = enum(u8) { + a, + b, + c, + }; + var x = Value.a; + + expect(@atomicLoad(Value, &x, .SeqCst) != .b); + + _ = @atomicRmw(Value, &x, .Xchg, .c, .SeqCst); + expect(@atomicLoad(Value, &x, .SeqCst) == .c); + expect(@atomicLoad(Value, &x, .SeqCst) != .a); + expect(@atomicLoad(Value, &x, .SeqCst) != .b); +} + +test "atomic store" { + var x: u32 = 0; + @atomicStore(u32, &x, 1, .SeqCst); + expect(@atomicLoad(u32, &x, .SeqCst) == 1); + @atomicStore(u32, &x, 12345678, .SeqCst); + expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); +} + +test "atomic store comptime" { + comptime testAtomicStore(); + testAtomicStore(); +} + +fn testAtomicStore() void { + var x: u32 = 0; + @atomicStore(u32, &x, 1, .SeqCst); + expect(@atomicLoad(u32, &x, .SeqCst) == 1); + @atomicStore(u32, &x, 12345678, .SeqCst); + expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); +} + +test "atomicrmw with floats" { + if (builtin.target.cpu.arch == .aarch64 or + builtin.target.cpu.arch == .arm or + builtin.target.cpu.arch == .riscv64) + { + // https://github.com/ziglang/zig/issues/4457 + return error.SkipZigTest; + } + testAtomicRmwFloat(); + comptime testAtomicRmwFloat(); +} + +fn testAtomicRmwFloat() void { + var x: f32 = 0; + expect(x == 0); + _ = @atomicRmw(f32, &x, .Xchg, 1, .SeqCst); + expect(x == 1); + _ = @atomicRmw(f32, &x, .Add, 5, .SeqCst); + expect(x == 6); + _ = @atomicRmw(f32, &x, .Sub, 2, .SeqCst); + expect(x == 4); +} + +test "atomicrmw with ints" { + testAtomicRmwInt(); + comptime testAtomicRmwInt(); +} + +fn testAtomicRmwInt() void { + var x: u8 = 1; + var res = @atomicRmw(u8, &x, .Xchg, 3, .SeqCst); + expect(x == 3 and res == 1); + _ = @atomicRmw(u8, &x, .Add, 3, .SeqCst); + expect(x == 6); + _ = @atomicRmw(u8, &x, .Sub, 1, .SeqCst); + expect(x == 5); + _ = @atomicRmw(u8, &x, .And, 4, .SeqCst); + expect(x == 4); + _ = @atomicRmw(u8, &x, .Nand, 4, .SeqCst); + expect(x == 0xfb); + _ = @atomicRmw(u8, &x, .Or, 6, .SeqCst); + expect(x == 0xff); + _ = @atomicRmw(u8, &x, .Xor, 2, .SeqCst); + expect(x == 0xfd); + + _ = @atomicRmw(u8, &x, .Max, 1, .SeqCst); + expect(x == 0xfd); + _ = @atomicRmw(u8, &x, .Min, 1, .SeqCst); + expect(x == 1); +} + +test "atomics with different types" { + testAtomicsWithType(bool, true, false); + inline for (.{ u1, i5, u15 }) |T| { + var x: T = 0; + testAtomicsWithType(T, 0, 1); + } + testAtomicsWithType(u0, 0, 0); + testAtomicsWithType(i0, 0, 0); +} + +fn testAtomicsWithType(comptime T: type, a: T, b: T) void { + var x: T = b; + @atomicStore(T, &x, a, .SeqCst); + expect(x == a); + expect(@atomicLoad(T, &x, .SeqCst) == a); + expect(@atomicRmw(T, &x, .Xchg, b, .SeqCst) == a); + expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst) == null); + if (@sizeOf(T) != 0) + expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst).? == a); +} diff --git a/test/behavior/await_struct.zig b/test/behavior/await_struct.zig new file mode 100644 index 0000000000..328f8f87fd --- /dev/null +++ b/test/behavior/await_struct.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; + +const Foo = struct { + x: i32, +}; + +var await_a_promise: anyframe = undefined; +var await_final_result = Foo{ .x = 0 }; + +test "coroutine await struct" { + await_seq('a'); + var p = async await_amain(); + await_seq('f'); + resume await_a_promise; + await_seq('i'); + expect(await_final_result.x == 1234); + expect(std.mem.eql(u8, &await_points, "abcdefghi")); +} +fn await_amain() callconv(.Async) void { + await_seq('b'); + var p = async await_another(); + await_seq('e'); + await_final_result = await p; + await_seq('h'); +} +fn await_another() callconv(.Async) Foo { + await_seq('c'); + suspend { + await_seq('d'); + await_a_promise = @frame(); + } + await_seq('g'); + return Foo{ .x = 1234 }; +} + +var await_points = [_]u8{0} ** "abcdefghi".len; +var await_seq_index: usize = 0; + +fn await_seq(c: u8) void { + await_points[await_seq_index] = c; + await_seq_index += 1; +} diff --git a/test/behavior/bit_shifting.zig b/test/behavior/bit_shifting.zig new file mode 100644 index 0000000000..746d598237 --- /dev/null +++ b/test/behavior/bit_shifting.zig @@ -0,0 +1,104 @@ +const std = @import("std"); +const expect = std.testing.expect; + +fn ShardedTable(comptime Key: type, comptime mask_bit_count: comptime_int, comptime V: type) type { + const key_bits = @typeInfo(Key).Int.bits; + expect(Key == std.meta.Int(.unsigned, key_bits)); + expect(key_bits >= mask_bit_count); + const shard_key_bits = mask_bit_count; + const ShardKey = std.meta.Int(.unsigned, mask_bit_count); + const shift_amount = key_bits - shard_key_bits; + return struct { + const Self = @This(); + shards: [1 << shard_key_bits]?*Node, + + pub fn create() Self { + return Self{ .shards = [_]?*Node{null} ** (1 << shard_key_bits) }; + } + + fn getShardKey(key: Key) ShardKey { + // https://github.com/ziglang/zig/issues/1544 + // this special case is needed because you can't u32 >> 32. + if (ShardKey == u0) return 0; + + // this can be u1 >> u0 + const shard_key = key >> shift_amount; + + // TODO: https://github.com/ziglang/zig/issues/1544 + // This cast could be implicit if we teach the compiler that + // u32 >> 30 -> u2 + return @intCast(ShardKey, shard_key); + } + + pub fn put(self: *Self, node: *Node) void { + const shard_key = Self.getShardKey(node.key); + node.next = self.shards[shard_key]; + self.shards[shard_key] = node; + } + + pub fn get(self: *Self, key: Key) ?*Node { + const shard_key = Self.getShardKey(key); + var maybe_node = self.shards[shard_key]; + while (maybe_node) |node| : (maybe_node = node.next) { + if (node.key == key) return node; + } + return null; + } + + pub const Node = struct { + key: Key, + value: V, + next: ?*Node, + + pub fn init(self: *Node, key: Key, value: V) void { + self.key = key; + self.value = value; + self.next = null; + } + }; + }; +} + +test "sharded table" { + // realistic 16-way sharding + testShardedTable(u32, 4, 8); + + testShardedTable(u5, 0, 32); // ShardKey == u0 + testShardedTable(u5, 2, 32); + testShardedTable(u5, 5, 32); + + testShardedTable(u1, 0, 2); + testShardedTable(u1, 1, 2); // this does u1 >> u0 + + testShardedTable(u0, 0, 1); +} +fn testShardedTable(comptime Key: type, comptime mask_bit_count: comptime_int, comptime node_count: comptime_int) void { + const Table = ShardedTable(Key, mask_bit_count, void); + + var table = Table.create(); + var node_buffer: [node_count]Table.Node = undefined; + for (node_buffer) |*node, i| { + const key = @intCast(Key, i); + expect(table.get(key) == null); + node.init(key, {}); + table.put(node); + } + + for (node_buffer) |*node, i| { + expect(table.get(@intCast(Key, i)) == node); + } +} + +// #2225 +test "comptime shr of BigInt" { + comptime { + var n0 = 0xdeadbeef0000000000000000; + std.debug.assert(n0 >> 64 == 0xdeadbeef); + var n1 = 17908056155735594659; + std.debug.assert(n1 >> 64 == 0); + } +} + +test "comptime shift safety check" { + const x = @as(usize, 42) << @sizeOf(usize); +} diff --git a/test/behavior/bitcast.zig b/test/behavior/bitcast.zig new file mode 100644 index 0000000000..abac1cc80a --- /dev/null +++ b/test/behavior/bitcast.zig @@ -0,0 +1,197 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const maxInt = std.math.maxInt; +const native_endian = builtin.target.cpu.arch.endian(); + +test "@bitCast i32 -> u32" { + testBitCast_i32_u32(); + comptime testBitCast_i32_u32(); +} + +fn testBitCast_i32_u32() void { + expect(conv(-1) == maxInt(u32)); + expect(conv2(maxInt(u32)) == -1); +} + +fn conv(x: i32) u32 { + return @bitCast(u32, x); +} +fn conv2(x: u32) i32 { + return @bitCast(i32, x); +} + +test "@bitCast extern enum to its integer type" { + const SOCK = extern enum { + A, + B, + + fn testBitCastExternEnum() void { + var SOCK_DGRAM = @This().B; + var sock_dgram = @bitCast(c_int, SOCK_DGRAM); + expect(sock_dgram == 1); + } + }; + + SOCK.testBitCastExternEnum(); + comptime SOCK.testBitCastExternEnum(); +} + +test "@bitCast packed structs at runtime and comptime" { + const Full = packed struct { + number: u16, + }; + const Divided = packed struct { + half1: u8, + quarter3: u4, + quarter4: u4, + }; + const S = struct { + fn doTheTest() void { + var full = Full{ .number = 0x1234 }; + var two_halves = @bitCast(Divided, full); + switch (native_endian) { + .Big => { + expect(two_halves.half1 == 0x12); + expect(two_halves.quarter3 == 0x3); + expect(two_halves.quarter4 == 0x4); + }, + .Little => { + expect(two_halves.half1 == 0x34); + expect(two_halves.quarter3 == 0x2); + expect(two_halves.quarter4 == 0x1); + }, + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "@bitCast extern structs at runtime and comptime" { + const Full = extern struct { + number: u16, + }; + const TwoHalves = extern struct { + half1: u8, + half2: u8, + }; + const S = struct { + fn doTheTest() void { + var full = Full{ .number = 0x1234 }; + var two_halves = @bitCast(TwoHalves, full); + switch (native_endian) { + .Big => { + expect(two_halves.half1 == 0x12); + expect(two_halves.half2 == 0x34); + }, + .Little => { + expect(two_halves.half1 == 0x34); + expect(two_halves.half2 == 0x12); + }, + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "bitcast packed struct to integer and back" { + const LevelUpMove = packed struct { + move_id: u9, + level: u7, + }; + const S = struct { + fn doTheTest() void { + var move = LevelUpMove{ .move_id = 1, .level = 2 }; + var v = @bitCast(u16, move); + var back_to_a_move = @bitCast(LevelUpMove, v); + expect(back_to_a_move.move_id == 1); + expect(back_to_a_move.level == 2); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "implicit cast to error union by returning" { + const S = struct { + fn entry() void { + expect((func(-1) catch unreachable) == maxInt(u64)); + } + pub fn func(sz: i64) anyerror!u64 { + return @bitCast(u64, sz); + } + }; + S.entry(); + comptime S.entry(); +} + +// issue #3010: compiler segfault +test "bitcast literal [4]u8 param to u32" { + const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 }); + expect(ip == maxInt(u32)); +} + +test "bitcast packed struct literal to byte" { + const Foo = packed struct { + value: u8, + }; + const casted = @bitCast(u8, Foo{ .value = 0xF }); + expect(casted == 0xf); +} + +test "comptime bitcast used in expression has the correct type" { + const Foo = packed struct { + value: u8, + }; + expect(@bitCast(u8, Foo{ .value = 0xF }) == 0xf); +} + +test "bitcast result to _" { + _ = @bitCast(u8, @as(i8, 1)); +} + +test "nested bitcast" { + const S = struct { + fn moo(x: isize) void { + @import("std").testing.expectEqual(@intCast(isize, 42), x); + } + + fn foo(x: isize) void { + @This().moo( + @bitCast(isize, if (x != 0) @bitCast(usize, x) else @bitCast(usize, x)), + ); + } + }; + + S.foo(42); + comptime S.foo(42); +} + +test "bitcast passed as tuple element" { + const S = struct { + fn foo(args: anytype) void { + comptime expect(@TypeOf(args[0]) == f32); + expect(args[0] == 12.34); + } + }; + S.foo(.{@bitCast(f32, @as(u32, 0x414570A4))}); +} + +test "triple level result location with bitcast sandwich passed as tuple element" { + const S = struct { + fn foo(args: anytype) void { + comptime expect(@TypeOf(args[0]) == f64); + expect(args[0] > 12.33 and args[0] < 12.35); + } + }; + S.foo(.{@as(f64, @bitCast(f32, @as(u32, 0x414570A4)))}); +} + +test "bitcast generates a temporary value" { + var y = @as(u16, 0x55AA); + const x = @bitCast(u16, @bitCast([2]u8, y)); + expectEqual(y, x); +} diff --git a/test/behavior/bitreverse.zig b/test/behavior/bitreverse.zig new file mode 100644 index 0000000000..8de2d5d2ca --- /dev/null +++ b/test/behavior/bitreverse.zig @@ -0,0 +1,69 @@ +const std = @import("std"); +const expect = std.testing.expect; +const minInt = std.math.minInt; + +test "@bitReverse" { + comptime testBitReverse(); + testBitReverse(); +} + +fn testBitReverse() void { + // using comptime_ints, unsigned + expect(@bitReverse(u0, 0) == 0); + expect(@bitReverse(u5, 0x12) == 0x9); + expect(@bitReverse(u8, 0x12) == 0x48); + expect(@bitReverse(u16, 0x1234) == 0x2c48); + expect(@bitReverse(u24, 0x123456) == 0x6a2c48); + expect(@bitReverse(u32, 0x12345678) == 0x1e6a2c48); + expect(@bitReverse(u40, 0x123456789a) == 0x591e6a2c48); + expect(@bitReverse(u48, 0x123456789abc) == 0x3d591e6a2c48); + expect(@bitReverse(u56, 0x123456789abcde) == 0x7b3d591e6a2c48); + expect(@bitReverse(u64, 0x123456789abcdef1) == 0x8f7b3d591e6a2c48); + expect(@bitReverse(u128, 0x123456789abcdef11121314151617181) == 0x818e868a828c84888f7b3d591e6a2c48); + + // using runtime uints, unsigned + var num0: u0 = 0; + expect(@bitReverse(u0, num0) == 0); + var num5: u5 = 0x12; + expect(@bitReverse(u5, num5) == 0x9); + var num8: u8 = 0x12; + expect(@bitReverse(u8, num8) == 0x48); + var num16: u16 = 0x1234; + expect(@bitReverse(u16, num16) == 0x2c48); + var num24: u24 = 0x123456; + expect(@bitReverse(u24, num24) == 0x6a2c48); + var num32: u32 = 0x12345678; + expect(@bitReverse(u32, num32) == 0x1e6a2c48); + var num40: u40 = 0x123456789a; + expect(@bitReverse(u40, num40) == 0x591e6a2c48); + var num48: u48 = 0x123456789abc; + expect(@bitReverse(u48, num48) == 0x3d591e6a2c48); + var num56: u56 = 0x123456789abcde; + expect(@bitReverse(u56, num56) == 0x7b3d591e6a2c48); + var num64: u64 = 0x123456789abcdef1; + expect(@bitReverse(u64, num64) == 0x8f7b3d591e6a2c48); + var num128: u128 = 0x123456789abcdef11121314151617181; + expect(@bitReverse(u128, num128) == 0x818e868a828c84888f7b3d591e6a2c48); + + // using comptime_ints, signed, positive + expect(@bitReverse(u8, @as(u8, 0)) == 0); + expect(@bitReverse(i8, @bitCast(i8, @as(u8, 0x92))) == @bitCast(i8, @as(u8, 0x49))); + expect(@bitReverse(i16, @bitCast(i16, @as(u16, 0x1234))) == @bitCast(i16, @as(u16, 0x2c48))); + expect(@bitReverse(i24, @bitCast(i24, @as(u24, 0x123456))) == @bitCast(i24, @as(u24, 0x6a2c48))); + expect(@bitReverse(i32, @bitCast(i32, @as(u32, 0x12345678))) == @bitCast(i32, @as(u32, 0x1e6a2c48))); + expect(@bitReverse(i40, @bitCast(i40, @as(u40, 0x123456789a))) == @bitCast(i40, @as(u40, 0x591e6a2c48))); + expect(@bitReverse(i48, @bitCast(i48, @as(u48, 0x123456789abc))) == @bitCast(i48, @as(u48, 0x3d591e6a2c48))); + expect(@bitReverse(i56, @bitCast(i56, @as(u56, 0x123456789abcde))) == @bitCast(i56, @as(u56, 0x7b3d591e6a2c48))); + expect(@bitReverse(i64, @bitCast(i64, @as(u64, 0x123456789abcdef1))) == @bitCast(i64, @as(u64, 0x8f7b3d591e6a2c48))); + expect(@bitReverse(i128, @bitCast(i128, @as(u128, 0x123456789abcdef11121314151617181))) == @bitCast(i128, @as(u128, 0x818e868a828c84888f7b3d591e6a2c48))); + + // using signed, negative. Compare to runtime ints returned from llvm. + var neg8: i8 = -18; + expect(@bitReverse(i8, @as(i8, -18)) == @bitReverse(i8, neg8)); + var neg16: i16 = -32694; + expect(@bitReverse(i16, @as(i16, -32694)) == @bitReverse(i16, neg16)); + var neg24: i24 = -6773785; + expect(@bitReverse(i24, @as(i24, -6773785)) == @bitReverse(i24, neg24)); + var neg32: i32 = -16773785; + expect(@bitReverse(i32, @as(i32, -16773785)) == @bitReverse(i32, neg32)); +} diff --git a/test/behavior/bool.zig b/test/behavior/bool.zig new file mode 100644 index 0000000000..ef9383244e --- /dev/null +++ b/test/behavior/bool.zig @@ -0,0 +1,35 @@ +const expect = @import("std").testing.expect; + +test "bool literals" { + expect(true); + expect(!false); +} + +test "cast bool to int" { + const t = true; + const f = false; + expect(@boolToInt(t) == @as(u32, 1)); + expect(@boolToInt(f) == @as(u32, 0)); + nonConstCastBoolToInt(t, f); +} + +fn nonConstCastBoolToInt(t: bool, f: bool) void { + expect(@boolToInt(t) == @as(u32, 1)); + expect(@boolToInt(f) == @as(u32, 0)); +} + +test "bool cmp" { + expect(testBoolCmp(true, false) == false); +} +fn testBoolCmp(a: bool, b: bool) bool { + return a == b; +} + +const global_f = false; +const global_t = true; +const not_global_f = !global_f; +const not_global_t = !global_t; +test "compile time bool not" { + expect(not_global_f); + expect(!not_global_t); +} diff --git a/test/behavior/bugs/1025.zig b/test/behavior/bugs/1025.zig new file mode 100644 index 0000000000..66e1a2be54 --- /dev/null +++ b/test/behavior/bugs/1025.zig @@ -0,0 +1,12 @@ +const A = struct { + B: type, +}; + +fn getA() A { + return A{ .B = u8 }; +} + +test "bug 1025" { + const a = getA(); + @import("std").testing.expect(a.B == u8); +} diff --git a/test/behavior/bugs/1076.zig b/test/behavior/bugs/1076.zig new file mode 100644 index 0000000000..fa3caf0df8 --- /dev/null +++ b/test/behavior/bugs/1076.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const mem = std.mem; +const expect = std.testing.expect; + +test "comptime code should not modify constant data" { + testCastPtrOfArrayToSliceAndPtr(); + comptime testCastPtrOfArrayToSliceAndPtr(); +} + +fn testCastPtrOfArrayToSliceAndPtr() void { + { + var array = "aoeu".*; + const x: [*]u8 = &array; + x[0] += 1; + expect(mem.eql(u8, array[0..], "boeu")); + } + { + var array: [4]u8 = "aoeu".*; + const x: [*]u8 = &array; + x[0] += 1; + expect(mem.eql(u8, array[0..], "boeu")); + } +} diff --git a/test/behavior/bugs/1111.zig b/test/behavior/bugs/1111.zig new file mode 100644 index 0000000000..607bc33666 --- /dev/null +++ b/test/behavior/bugs/1111.zig @@ -0,0 +1,11 @@ +const Foo = extern enum { + Bar = -1, +}; + +test "issue 1111 fixed" { + const v = Foo.Bar; + + switch (v) { + Foo.Bar => return, + } +} diff --git a/test/behavior/bugs/1120.zig b/test/behavior/bugs/1120.zig new file mode 100644 index 0000000000..dda46e8e1c --- /dev/null +++ b/test/behavior/bugs/1120.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const A = packed struct { + a: u2, + b: u6, +}; +const B = packed struct { + q: u8, + a: u2, + b: u6, +}; +test "bug 1120" { + var a = A{ .a = 2, .b = 2 }; + var b = B{ .q = 22, .a = 3, .b = 2 }; + var t: usize = 0; + const ptr = switch (t) { + 0 => &a.a, + 1 => &b.a, + else => unreachable, + }; + expect(ptr.* == 2); +} diff --git a/test/behavior/bugs/1277.zig b/test/behavior/bugs/1277.zig new file mode 100644 index 0000000000..3aa1db2ea0 --- /dev/null +++ b/test/behavior/bugs/1277.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +const S = struct { + f: ?fn () i32, +}; + +const s = S{ .f = f }; + +fn f() i32 { + return 1234; +} + +test "don't emit an LLVM global for a const function when it's in an optional in a struct" { + std.testing.expect(s.f.?() == 1234); +} diff --git a/test/behavior/bugs/1310.zig b/test/behavior/bugs/1310.zig new file mode 100644 index 0000000000..788cba5756 --- /dev/null +++ b/test/behavior/bugs/1310.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const expect = std.testing.expect; + +pub const VM = ?[*]const struct_InvocationTable_; +pub const struct_InvocationTable_ = extern struct { + GetVM: ?fn (?[*]VM) callconv(.C) c_int, +}; + +pub const struct_VM_ = extern struct { + functions: ?[*]const struct_InvocationTable_, +}; + +//excised output from stdlib.h etc + +pub const InvocationTable_ = struct_InvocationTable_; +pub const VM_ = struct_VM_; + +fn agent_callback(_vm: [*]VM, options: [*]u8) callconv(.C) i32 { + return 11; +} + +test "fixed" { + expect(agent_callback(undefined, undefined) == 11); +} diff --git a/test/behavior/bugs/1322.zig b/test/behavior/bugs/1322.zig new file mode 100644 index 0000000000..02ead6afff --- /dev/null +++ b/test/behavior/bugs/1322.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +const B = union(enum) { + c: C, + None, +}; + +const A = struct { + b: B, +}; + +const C = struct {}; + +test "tagged union with all void fields but a meaningful tag" { + var a: A = A{ .b = B{ .c = C{} } }; + std.testing.expect(@as(std.meta.Tag(B), a.b) == std.meta.Tag(B).c); + a = A{ .b = B.None }; + std.testing.expect(@as(std.meta.Tag(B), a.b) == std.meta.Tag(B).None); +} diff --git a/test/behavior/bugs/1381.zig b/test/behavior/bugs/1381.zig new file mode 100644 index 0000000000..1d44593990 --- /dev/null +++ b/test/behavior/bugs/1381.zig @@ -0,0 +1,21 @@ +const std = @import("std"); + +const B = union(enum) { + D: u8, + E: u16, +}; + +const A = union(enum) { + B: B, + C: u8, +}; + +test "union that needs padding bytes inside an array" { + var as = [_]A{ + A{ .B = B{ .D = 1 } }, + A{ .B = B{ .D = 1 } }, + }; + + const a = as[0].B; + std.testing.expect(a.D == 1); +} diff --git a/test/behavior/bugs/1421.zig b/test/behavior/bugs/1421.zig new file mode 100644 index 0000000000..4cc3a90b29 --- /dev/null +++ b/test/behavior/bugs/1421.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const S = struct { + fn method() std.builtin.TypeInfo { + return @typeInfo(S); + } +}; + +test "functions with return type required to be comptime are generic" { + const ti = S.method(); + expect(@as(std.builtin.TypeId, ti) == std.builtin.TypeId.Struct); +} diff --git a/test/behavior/bugs/1442.zig b/test/behavior/bugs/1442.zig new file mode 100644 index 0000000000..d5ea3f66fe --- /dev/null +++ b/test/behavior/bugs/1442.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +const Union = union(enum) { + Text: []const u8, + Color: u32, +}; + +test "const error union field alignment" { + var union_or_err: anyerror!Union = Union{ .Color = 1234 }; + std.testing.expect((union_or_err catch unreachable).Color == 1234); +} diff --git a/test/behavior/bugs/1467.zig b/test/behavior/bugs/1467.zig new file mode 100644 index 0000000000..71c55dc59c --- /dev/null +++ b/test/behavior/bugs/1467.zig @@ -0,0 +1,7 @@ +pub const E = enum(u32) { A, B, C }; +pub const S = extern struct { + e: E, +}; +test "bug 1467" { + const s: S = undefined; +} diff --git a/test/behavior/bugs/1486.zig b/test/behavior/bugs/1486.zig new file mode 100644 index 0000000000..1b8e5ca4a1 --- /dev/null +++ b/test/behavior/bugs/1486.zig @@ -0,0 +1,10 @@ +const expect = @import("std").testing.expect; + +const ptr = &global; +var global: u64 = 123; + +test "constant pointer to global variable causes runtime load" { + global = 1234; + expect(&global == ptr); + expect(ptr.* == 1234); +} diff --git a/test/behavior/bugs/1500.zig b/test/behavior/bugs/1500.zig new file mode 100644 index 0000000000..e24cfe5a9c --- /dev/null +++ b/test/behavior/bugs/1500.zig @@ -0,0 +1,10 @@ +const A = struct { + b: B, +}; + +const B = fn (A) void; + +test "allow these dependencies" { + var a: A = undefined; + var b: B = undefined; +} diff --git a/test/behavior/bugs/1607.zig b/test/behavior/bugs/1607.zig new file mode 100644 index 0000000000..ffc1aa85dc --- /dev/null +++ b/test/behavior/bugs/1607.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const testing = std.testing; + +const a = [_]u8{ 1, 2, 3 }; + +fn checkAddress(s: []const u8) void { + for (s) |*i, j| { + testing.expect(i == &a[j]); + } +} + +test "slices pointing at the same address as global array." { + checkAddress(&a); + comptime checkAddress(&a); +} diff --git a/test/behavior/bugs/1735.zig b/test/behavior/bugs/1735.zig new file mode 100644 index 0000000000..e757b5157c --- /dev/null +++ b/test/behavior/bugs/1735.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +const mystruct = struct { + pending: ?listofstructs, +}; +pub fn TailQueue(comptime T: type) type { + return struct { + const Self = @This(); + + pub const Node = struct { + prev: ?*Node, + next: ?*Node, + data: T, + }; + + first: ?*Node, + last: ?*Node, + len: usize, + + pub fn init() Self { + return Self{ + .first = null, + .last = null, + .len = 0, + }; + } + }; +} +const listofstructs = TailQueue(mystruct); + +const a = struct { + const Self = @This(); + + foo: listofstructs, + + pub fn init() Self { + return Self{ + .foo = listofstructs.init(), + }; + } +}; + +test "intialization" { + var t = a.init(); + std.testing.expect(t.foo.len == 0); +} diff --git a/test/behavior/bugs/1741.zig b/test/behavior/bugs/1741.zig new file mode 100644 index 0000000000..9882536d38 --- /dev/null +++ b/test/behavior/bugs/1741.zig @@ -0,0 +1,6 @@ +const std = @import("std"); + +test "fixed" { + const x: f32 align(128) = 12.34; + std.testing.expect(@ptrToInt(&x) % 128 == 0); +} diff --git a/test/behavior/bugs/1851.zig b/test/behavior/bugs/1851.zig new file mode 100644 index 0000000000..d6cf17651d --- /dev/null +++ b/test/behavior/bugs/1851.zig @@ -0,0 +1,26 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "allocation and looping over 3-byte integer" { + expect(@sizeOf(u24) == 4); + expect(@sizeOf([1]u24) == 4); + expect(@alignOf(u24) == 4); + expect(@alignOf([1]u24) == 4); + + var x = try std.testing.allocator.alloc(u24, 2); + defer std.testing.allocator.free(x); + expect(x.len == 2); + x[0] = 0xFFFFFF; + x[1] = 0xFFFFFF; + + const bytes = std.mem.sliceAsBytes(x); + expect(@TypeOf(bytes) == []align(4) u8); + expect(bytes.len == 8); + + for (bytes) |*b| { + b.* = 0x00; + } + + expect(x[0] == 0x00); + expect(x[1] == 0x00); +} diff --git a/test/behavior/bugs/1914.zig b/test/behavior/bugs/1914.zig new file mode 100644 index 0000000000..2c9e836e6a --- /dev/null +++ b/test/behavior/bugs/1914.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +const A = struct { + b_list_pointer: *const []B, +}; +const B = struct { + a_pointer: *const A, +}; + +const b_list: []B = &[_]B{}; +const a = A{ .b_list_pointer = &b_list }; + +test "segfault bug" { + const assert = std.debug.assert; + const obj = B{ .a_pointer = &a }; + assert(obj.a_pointer == &a); // this makes zig crash +} + +const A2 = struct { + pointer: *B, +}; + +pub const B2 = struct { + pointer_array: []*A2, +}; + +var b_value = B2{ .pointer_array = &[_]*A2{} }; + +test "basic stuff" { + std.debug.assert(&b_value == &b_value); +} diff --git a/test/behavior/bugs/2006.zig b/test/behavior/bugs/2006.zig new file mode 100644 index 0000000000..dad78a9ba0 --- /dev/null +++ b/test/behavior/bugs/2006.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const S = struct { + p: *S, +}; +test "bug 2006" { + var a: S = undefined; + a = S{ .p = undefined }; + expect(@sizeOf(S) != 0); + expect(@sizeOf(*void) == 0); +} diff --git a/test/behavior/bugs/2114.zig b/test/behavior/bugs/2114.zig new file mode 100644 index 0000000000..1034a256d3 --- /dev/null +++ b/test/behavior/bugs/2114.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const expect = std.testing.expect; +const math = std.math; + +fn ctz(x: anytype) usize { + return @ctz(@TypeOf(x), x); +} + +test "fixed" { + testClz(); + comptime testClz(); +} + +fn testClz() void { + expect(ctz(@as(u128, 0x40000000000000000000000000000000)) == 126); + expect(math.rotl(u128, @as(u128, 0x40000000000000000000000000000000), @as(u8, 1)) == @as(u128, 0x80000000000000000000000000000000)); + expect(ctz(@as(u128, 0x80000000000000000000000000000000)) == 127); + expect(ctz(math.rotl(u128, @as(u128, 0x40000000000000000000000000000000), @as(u8, 1))) == 127); +} diff --git a/test/behavior/bugs/2346.zig b/test/behavior/bugs/2346.zig new file mode 100644 index 0000000000..c8cea34813 --- /dev/null +++ b/test/behavior/bugs/2346.zig @@ -0,0 +1,6 @@ +test "fixed" { + const a: *void = undefined; + const b: *[1]void = a; + const c: *[0]u8 = undefined; + const d: []u8 = c; +} diff --git a/test/behavior/bugs/2578.zig b/test/behavior/bugs/2578.zig new file mode 100644 index 0000000000..cf7d941562 --- /dev/null +++ b/test/behavior/bugs/2578.zig @@ -0,0 +1,12 @@ +const Foo = struct { + y: u8, +}; + +var foo: Foo = undefined; +const t = &foo; + +fn bar(pointer: ?*c_void) void {} + +test "fixed" { + bar(t); +} diff --git a/test/behavior/bugs/2692.zig b/test/behavior/bugs/2692.zig new file mode 100644 index 0000000000..267c3a131a --- /dev/null +++ b/test/behavior/bugs/2692.zig @@ -0,0 +1,6 @@ +fn foo(a: []u8) void {} + +test "address of 0 length array" { + var pt: [0]u8 = undefined; + foo(&pt); +} diff --git a/test/behavior/bugs/2889.zig b/test/behavior/bugs/2889.zig new file mode 100644 index 0000000000..4991b56510 --- /dev/null +++ b/test/behavior/bugs/2889.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +const source = "A-"; + +fn parseNote() ?i32 { + const letter = source[0]; + const modifier = source[1]; + + const semitone = blk: { + if (letter == 'C' and modifier == '-') break :blk @as(i32, 0); + if (letter == 'C' and modifier == '#') break :blk @as(i32, 1); + if (letter == 'D' and modifier == '-') break :blk @as(i32, 2); + if (letter == 'D' and modifier == '#') break :blk @as(i32, 3); + if (letter == 'E' and modifier == '-') break :blk @as(i32, 4); + if (letter == 'F' and modifier == '-') break :blk @as(i32, 5); + if (letter == 'F' and modifier == '#') break :blk @as(i32, 6); + if (letter == 'G' and modifier == '-') break :blk @as(i32, 7); + if (letter == 'G' and modifier == '#') break :blk @as(i32, 8); + if (letter == 'A' and modifier == '-') break :blk @as(i32, 9); + if (letter == 'A' and modifier == '#') break :blk @as(i32, 10); + if (letter == 'B' and modifier == '-') break :blk @as(i32, 11); + return null; + }; + + return semitone; +} + +test "fixed" { + const result = parseNote(); + std.testing.expect(result.? == 9); +} diff --git a/test/behavior/bugs/3007.zig b/test/behavior/bugs/3007.zig new file mode 100644 index 0000000000..b723ebc097 --- /dev/null +++ b/test/behavior/bugs/3007.zig @@ -0,0 +1,23 @@ +const std = @import("std"); + +const Foo = struct { + free: bool, + + pub const FooError = error{NotFree}; +}; + +var foo = Foo{ .free = true }; +var default_foo: ?*Foo = null; + +fn get_foo() Foo.FooError!*Foo { + if (foo.free) { + foo.free = false; + return &foo; + } + return error.NotFree; +} + +test "fixed" { + default_foo = get_foo() catch null; // This Line + std.testing.expect(!default_foo.?.free); +} diff --git a/test/behavior/bugs/3046.zig b/test/behavior/bugs/3046.zig new file mode 100644 index 0000000000..b62474f9ba --- /dev/null +++ b/test/behavior/bugs/3046.zig @@ -0,0 +1,19 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const SomeStruct = struct { + field: i32, +}; + +fn couldFail() anyerror!i32 { + return 1; +} + +var some_struct: SomeStruct = undefined; + +test "fixed" { + some_struct = SomeStruct{ + .field = couldFail() catch |_| @as(i32, 0), + }; + expect(some_struct.field == 1); +} diff --git a/test/behavior/bugs/3112.zig b/test/behavior/bugs/3112.zig new file mode 100644 index 0000000000..1dfe359d4a --- /dev/null +++ b/test/behavior/bugs/3112.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const State = struct { + const Self = @This(); + enter: fn (previous: ?Self) void, +}; + +fn prev(p: ?State) void { + expect(p == null); +} + +test "zig test crash" { + var global: State = undefined; + global.enter = prev; + global.enter(null); +} diff --git a/test/behavior/bugs/3367.zig b/test/behavior/bugs/3367.zig new file mode 100644 index 0000000000..3df3adbff7 --- /dev/null +++ b/test/behavior/bugs/3367.zig @@ -0,0 +1,12 @@ +const Foo = struct { + usingnamespace Mixin; +}; + +const Mixin = struct { + pub fn two(self: Foo) void {} +}; + +test "container member access usingnamespace decls" { + var foo = Foo{}; + foo.two(); +} diff --git a/test/behavior/bugs/3384.zig b/test/behavior/bugs/3384.zig new file mode 100644 index 0000000000..789b0be516 --- /dev/null +++ b/test/behavior/bugs/3384.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "resolve array slice using builtin" { + expect(@hasDecl(@This(), "std") == true); + expect(@hasDecl(@This(), "std"[0..0]) == false); + expect(@hasDecl(@This(), "std"[0..1]) == false); + expect(@hasDecl(@This(), "std"[0..2]) == false); + expect(@hasDecl(@This(), "std"[0..3]) == true); + expect(@hasDecl(@This(), "std"[0..]) == true); +} diff --git a/test/behavior/bugs/3468.zig b/test/behavior/bugs/3468.zig new file mode 100644 index 0000000000..adf3db3306 --- /dev/null +++ b/test/behavior/bugs/3468.zig @@ -0,0 +1,6 @@ +// zig fmt: off +test "pointer deref next to assignment" { + var a:i32=2; + var b=&a; + b.*=3; +} diff --git a/test/behavior/bugs/3586.zig b/test/behavior/bugs/3586.zig new file mode 100644 index 0000000000..7bb1ba5d31 --- /dev/null +++ b/test/behavior/bugs/3586.zig @@ -0,0 +1,11 @@ +const NoteParams = struct {}; + +const Container = struct { + params: ?NoteParams, +}; + +test "fixed" { + var ctr = Container{ + .params = NoteParams{}, + }; +} diff --git a/test/behavior/bugs/3742.zig b/test/behavior/bugs/3742.zig new file mode 100644 index 0000000000..bf6e1f5207 --- /dev/null +++ b/test/behavior/bugs/3742.zig @@ -0,0 +1,38 @@ +const std = @import("std"); + +pub const GET = struct { + key: []const u8, + + pub fn init(key: []const u8) GET { + return .{ .key = key }; + } + + pub const Redis = struct { + pub const Command = struct { + pub fn serialize(self: GET, comptime rootSerializer: type) void { + return rootSerializer.serializeCommand(.{ "GET", self.key }); + } + }; + }; +}; + +pub fn isCommand(comptime T: type) bool { + const tid = @typeInfo(T); + return (tid == .Struct or tid == .Enum or tid == .Union) and + @hasDecl(T, "Redis") and @hasDecl(T.Redis, "Command"); +} + +pub const ArgSerializer = struct { + pub fn serializeCommand(command: anytype) void { + const CmdT = @TypeOf(command); + + if (comptime isCommand(CmdT)) { + // COMMENTING THE NEXT LINE REMOVES THE ERROR + return CmdT.Redis.Command.serialize(command, ArgSerializer); + } + } +}; + +test "fixed" { + ArgSerializer.serializeCommand(GET.init("banana")); +} diff --git a/test/behavior/bugs/394.zig b/test/behavior/bugs/394.zig new file mode 100644 index 0000000000..b1f0b6b605 --- /dev/null +++ b/test/behavior/bugs/394.zig @@ -0,0 +1,18 @@ +const E = union(enum) { + A: [9]u8, + B: u64, +}; +const S = struct { + x: u8, + y: E, +}; + +const expect = @import("std").testing.expect; + +test "bug 394 fixed" { + const x = S{ + .x = 3, + .y = E{ .B = 1 }, + }; + expect(x.x == 3); +} diff --git a/test/behavior/bugs/421.zig b/test/behavior/bugs/421.zig new file mode 100644 index 0000000000..748cb3a62d --- /dev/null +++ b/test/behavior/bugs/421.zig @@ -0,0 +1,15 @@ +const expect = @import("std").testing.expect; + +test "bitCast to array" { + comptime testBitCastArray(); + testBitCastArray(); +} + +fn testBitCastArray() void { + expect(extractOne64(0x0123456789abcdef0123456789abcdef) == 0x0123456789abcdef); +} + +fn extractOne64(a: u128) u64 { + const x = @bitCast([2]u64, a); + return x[1]; +} diff --git a/test/behavior/bugs/4328.zig b/test/behavior/bugs/4328.zig new file mode 100644 index 0000000000..98ab7bd155 --- /dev/null +++ b/test/behavior/bugs/4328.zig @@ -0,0 +1,71 @@ +const expectEqual = @import("std").testing.expectEqual; + +const FILE = extern struct { + dummy_field: u8, +}; + +extern fn printf([*c]const u8, ...) c_int; +extern fn fputs([*c]const u8, noalias [*c]FILE) c_int; +extern fn ftell([*c]FILE) c_long; +extern fn fopen([*c]const u8, [*c]const u8) [*c]FILE; + +const S = extern struct { + state: c_short, + + extern fn s_do_thing([*c]S, b: c_int) c_short; +}; + +test "Extern function calls in @TypeOf" { + const Test = struct { + fn test_fn_1(a: anytype, b: anytype) @TypeOf(printf("%d %s\n", a, b)) { + return 0; + } + + fn test_fn_2(a: anytype) @TypeOf((S{ .state = 0 }).s_do_thing(a)) { + return 1; + } + + fn doTheTest() void { + expectEqual(c_int, @TypeOf(test_fn_1(0, 42))); + expectEqual(c_short, @TypeOf(test_fn_2(0))); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} + +test "Peer resolution of extern function calls in @TypeOf" { + const Test = struct { + fn test_fn() @TypeOf(ftell(null), fputs(null, null)) { + return 0; + } + + fn doTheTest() void { + expectEqual(c_long, @TypeOf(test_fn())); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} + +test "Extern function calls, dereferences and field access in @TypeOf" { + const Test = struct { + fn test_fn_1(a: c_long) @TypeOf(fopen("test", "r").*) { + return .{ .dummy_field = 0 }; + } + + fn test_fn_2(a: anytype) @TypeOf(fopen("test", "r").*.dummy_field) { + return 255; + } + + fn doTheTest() void { + expectEqual(FILE, @TypeOf(test_fn_1(0))); + expectEqual(u8, @TypeOf(test_fn_2(0))); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} diff --git a/test/behavior/bugs/4560.zig b/test/behavior/bugs/4560.zig new file mode 100644 index 0000000000..6821527894 --- /dev/null +++ b/test/behavior/bugs/4560.zig @@ -0,0 +1,32 @@ +const std = @import("std"); + +test "fixed" { + var s: S = .{ + .a = 1, + .b = .{ + .size = 123, + .max_distance_from_start_index = 456, + }, + }; + std.testing.expect(s.a == 1); + std.testing.expect(s.b.size == 123); + std.testing.expect(s.b.max_distance_from_start_index == 456); +} + +const S = struct { + a: u32, + b: Map, + + const Map = StringHashMap(*S); +}; + +pub fn StringHashMap(comptime V: type) type { + return HashMap([]const u8, V); +} + +pub fn HashMap(comptime K: type, comptime V: type) type { + return struct { + size: usize, + max_distance_from_start_index: usize, + }; +} diff --git a/test/behavior/bugs/4769_a.zig b/test/behavior/bugs/4769_a.zig new file mode 100644 index 0000000000..ab0c01417a --- /dev/null +++ b/test/behavior/bugs/4769_a.zig @@ -0,0 +1 @@ +//
\ No newline at end of file diff --git a/test/behavior/bugs/4769_b.zig b/test/behavior/bugs/4769_b.zig new file mode 100644 index 0000000000..23b2513f17 --- /dev/null +++ b/test/behavior/bugs/4769_b.zig @@ -0,0 +1 @@ +//!
\ No newline at end of file diff --git a/test/behavior/bugs/4769_c.zig b/test/behavior/bugs/4769_c.zig new file mode 100644 index 0000000000..4894ddf7e8 --- /dev/null +++ b/test/behavior/bugs/4769_c.zig @@ -0,0 +1 @@ +///
\ No newline at end of file diff --git a/test/behavior/bugs/4954.zig b/test/behavior/bugs/4954.zig new file mode 100644 index 0000000000..b5a9bdf851 --- /dev/null +++ b/test/behavior/bugs/4954.zig @@ -0,0 +1,8 @@ +fn f(buf: []u8) void { + var ptr = &buf[@sizeOf(u32)]; +} + +test "crash" { + var buf: [4096]u8 = undefined; + f(&buf); +} diff --git a/test/behavior/bugs/529.zig b/test/behavior/bugs/529.zig new file mode 100644 index 0000000000..6aeb73c331 --- /dev/null +++ b/test/behavior/bugs/529.zig @@ -0,0 +1,14 @@ +const A = extern struct { + field: c_int, +}; + +extern fn issue529(?*A) void; + +comptime { + _ = @import("529_other_file_2.zig"); +} + +test "issue 529 fixed" { + @import("529_other_file.zig").issue529(null); + issue529(null); +} diff --git a/test/behavior/bugs/529_other_file.zig b/test/behavior/bugs/529_other_file.zig new file mode 100644 index 0000000000..4ee4e31a10 --- /dev/null +++ b/test/behavior/bugs/529_other_file.zig @@ -0,0 +1,5 @@ +pub const A = extern struct { + field: c_int, +}; + +pub extern fn issue529(?*A) void; diff --git a/test/behavior/bugs/529_other_file_2.zig b/test/behavior/bugs/529_other_file_2.zig new file mode 100644 index 0000000000..b493959e50 --- /dev/null +++ b/test/behavior/bugs/529_other_file_2.zig @@ -0,0 +1,4 @@ +pub const A = extern struct { + field: c_int, +}; +export fn issue529(a: ?*A) void {} diff --git a/test/behavior/bugs/5398.zig b/test/behavior/bugs/5398.zig new file mode 100644 index 0000000000..fdfd0b3698 --- /dev/null +++ b/test/behavior/bugs/5398.zig @@ -0,0 +1,31 @@ +const std = @import("std"); +const testing = std.testing; + +pub const Mesh = struct { + id: u32, +}; +pub const Material = struct { + transparent: bool = true, + emits_shadows: bool = true, + render_color: bool = true, +}; +pub const Renderable = struct { + material: Material, + // The compiler inserts some padding here to ensure Mesh is correctly aligned. + mesh: Mesh, +}; + +var renderable: Renderable = undefined; + +test "assignment of field with padding" { + renderable = Renderable{ + .mesh = Mesh{ .id = 0 }, + .material = Material{ + .transparent = false, + .emits_shadows = false, + }, + }; + testing.expectEqual(false, renderable.material.transparent); + testing.expectEqual(false, renderable.material.emits_shadows); + testing.expectEqual(true, renderable.material.render_color); +} diff --git a/test/behavior/bugs/5413.zig b/test/behavior/bugs/5413.zig new file mode 100644 index 0000000000..5ef533a761 --- /dev/null +++ b/test/behavior/bugs/5413.zig @@ -0,0 +1,6 @@ +const expect = @import("std").testing.expect; + +test "Peer type resolution with string literals and unknown length u8 pointers" { + expect(@TypeOf("", "a", @as([*:0]const u8, "")) == [*:0]const u8); + expect(@TypeOf(@as([*:0]const u8, "baz"), "foo", "bar") == [*:0]const u8); +} diff --git a/test/behavior/bugs/5474.zig b/test/behavior/bugs/5474.zig new file mode 100644 index 0000000000..88ce96095e --- /dev/null +++ b/test/behavior/bugs/5474.zig @@ -0,0 +1,57 @@ +const std = @import("std"); + +// baseline (control) struct with array of scalar +const Box0 = struct { + items: [4]Item, + + const Item = struct { + num: u32, + }; +}; + +// struct with array of empty struct +const Box1 = struct { + items: [4]Item, + + const Item = struct {}; +}; + +// struct with array of zero-size struct +const Box2 = struct { + items: [4]Item, + + const Item = struct { + nothing: void, + }; +}; + +fn doTest() void { + // var + { + var box0: Box0 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == false); + + var box1: Box1 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == false); + + var box2: Box2 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == false); + } + + // const + { + const box0: Box0 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box0.items[0..])).Pointer.is_const == true); + + const box1: Box1 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box1.items[0..])).Pointer.is_const == true); + + const box2: Box2 = .{ .items = undefined }; + std.testing.expect(@typeInfo(@TypeOf(box2.items[0..])).Pointer.is_const == true); + } +} + +test "pointer-to-array constness for zero-size elements" { + doTest(); + comptime doTest(); +} diff --git a/test/behavior/bugs/5487.zig b/test/behavior/bugs/5487.zig new file mode 100644 index 0000000000..bf530a8a02 --- /dev/null +++ b/test/behavior/bugs/5487.zig @@ -0,0 +1,12 @@ +const io = @import("std").io; + +pub fn write(_: void, bytes: []const u8) !usize { + return 0; +} +pub fn writer() io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write) { + return io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write){ .context = {} }; +} + +test "crash" { + _ = io.multiWriter(.{writer()}); +} diff --git a/test/behavior/bugs/624.zig b/test/behavior/bugs/624.zig new file mode 100644 index 0000000000..1eee889a0d --- /dev/null +++ b/test/behavior/bugs/624.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const TestContext = struct { + server_context: *ListenerContext, +}; + +const ListenerContext = struct { + context_alloc: *ContextAllocator, +}; + +const ContextAllocator = MemoryPool(TestContext); + +fn MemoryPool(comptime T: type) type { + return struct { + n: usize, + }; +} + +test "foo" { + var allocator = ContextAllocator{ .n = 10 }; + expect(allocator.n == 10); +} diff --git a/test/behavior/bugs/6456.zig b/test/behavior/bugs/6456.zig new file mode 100644 index 0000000000..4b59792174 --- /dev/null +++ b/test/behavior/bugs/6456.zig @@ -0,0 +1,42 @@ +const std = @import("std"); +const testing = std.testing; +const StructField = std.builtin.TypeInfo.StructField; +const Declaration = std.builtin.TypeInfo.Declaration; + +const text = + \\f1 + \\f2 + \\f3 +; + +test "issue 6456" { + comptime { + var fields: []const StructField = &[0]StructField{}; + + var it = std.mem.tokenize(text, "\n"); + while (it.next()) |name| { + fields = fields ++ &[_]StructField{StructField{ + .alignment = 0, + .name = name, + .field_type = usize, + .default_value = @as(?usize, null), + .is_comptime = false, + }}; + } + + const T = @Type(.{ + .Struct = .{ + .layout = .Auto, + .is_tuple = false, + .fields = fields, + .decls = &[_]Declaration{}, + }, + }); + + const gen_fields = @typeInfo(T).Struct.fields; + testing.expectEqual(3, gen_fields.len); + testing.expectEqualStrings("f1", gen_fields[0].name); + testing.expectEqualStrings("f2", gen_fields[1].name); + testing.expectEqualStrings("f3", gen_fields[2].name); + } +} diff --git a/test/behavior/bugs/655.zig b/test/behavior/bugs/655.zig new file mode 100644 index 0000000000..3d1bccb183 --- /dev/null +++ b/test/behavior/bugs/655.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +const other_file = @import("655_other_file.zig"); + +test "function with *const parameter with type dereferenced by namespace" { + const x: other_file.Integer = 1234; + comptime std.testing.expect(@TypeOf(&x) == *const other_file.Integer); + foo(&x); +} + +fn foo(x: *const other_file.Integer) void { + std.testing.expect(x.* == 1234); +} diff --git a/test/behavior/bugs/655_other_file.zig b/test/behavior/bugs/655_other_file.zig new file mode 100644 index 0000000000..df1df44955 --- /dev/null +++ b/test/behavior/bugs/655_other_file.zig @@ -0,0 +1 @@ +pub const Integer = u32; diff --git a/test/behavior/bugs/656.zig b/test/behavior/bugs/656.zig new file mode 100644 index 0000000000..159ec52d43 --- /dev/null +++ b/test/behavior/bugs/656.zig @@ -0,0 +1,31 @@ +const expect = @import("std").testing.expect; + +const PrefixOp = union(enum) { + Return, + AddrOf: Value, +}; + +const Value = struct { + align_expr: ?u32, +}; + +test "optional if after an if in a switch prong of a switch with 2 prongs in an else" { + foo(false, true); +} + +fn foo(a: bool, b: bool) void { + var prefix_op = PrefixOp{ + .AddrOf = Value{ .align_expr = 1234 }, + }; + if (a) {} else { + switch (prefix_op) { + PrefixOp.AddrOf => |addr_of_info| { + if (b) {} + if (addr_of_info.align_expr) |align_expr| { + expect(align_expr == 1234); + } + }, + PrefixOp.Return => {}, + } + } +} diff --git a/test/behavior/bugs/6781.zig b/test/behavior/bugs/6781.zig new file mode 100644 index 0000000000..beadd97f1d --- /dev/null +++ b/test/behavior/bugs/6781.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const assert = std.debug.assert; + +const segfault = true; + +pub const JournalHeader = packed struct { + hash_chain_root: u128 = undefined, + prev_hash_chain_root: u128, + checksum: u128 = undefined, + magic: u64, + command: u32, + size: u32, + + pub fn calculate_checksum(self: *const JournalHeader, entry: []const u8) u128 { + assert(entry.len >= @sizeOf(JournalHeader)); + assert(entry.len == self.size); + + const checksum_offset = @byteOffsetOf(JournalHeader, "checksum"); + const checksum_size = @sizeOf(@TypeOf(self.checksum)); + assert(checksum_offset == 0 + 16 + 16); + assert(checksum_size == 16); + + var target: [32]u8 = undefined; + std.crypto.hash.Blake3.hash(entry[checksum_offset + checksum_size ..], target[0..], .{}); + return @bitCast(u128, target[0..checksum_size].*); + } + + pub fn calculate_hash_chain_root(self: *const JournalHeader) u128 { + const hash_chain_root_size = @sizeOf(@TypeOf(self.hash_chain_root)); + assert(hash_chain_root_size == 16); + + const prev_hash_chain_root_offset = @byteOffsetOf(JournalHeader, "prev_hash_chain_root"); + const prev_hash_chain_root_size = @sizeOf(@TypeOf(self.prev_hash_chain_root)); + assert(prev_hash_chain_root_offset == 0 + 16); + assert(prev_hash_chain_root_size == 16); + + const checksum_offset = @byteOffsetOf(JournalHeader, "checksum"); + const checksum_size = @sizeOf(@TypeOf(self.checksum)); + assert(checksum_offset == 0 + 16 + 16); + assert(checksum_size == 16); + + assert(prev_hash_chain_root_offset + prev_hash_chain_root_size == checksum_offset); + + const header = @bitCast([@sizeOf(JournalHeader)]u8, self.*); + const source = header[prev_hash_chain_root_offset .. checksum_offset + checksum_size]; + assert(source.len == prev_hash_chain_root_size + checksum_size); + var target: [32]u8 = undefined; + std.crypto.hash.Blake3.hash(source, target[0..], .{}); + if (segfault) { + return @bitCast(u128, target[0..hash_chain_root_size].*); + } else { + var array = target[0..hash_chain_root_size].*; + return @bitCast(u128, array); + } + } + + pub fn set_checksum_and_hash_chain_root(self: *JournalHeader, entry: []const u8) void { + self.checksum = self.calculate_checksum(entry); + self.hash_chain_root = self.calculate_hash_chain_root(); + } +}; + +test "fixed" { + var buffer = [_]u8{0} ** 65536; + var entry = std.mem.bytesAsValue(JournalHeader, buffer[0..@sizeOf(JournalHeader)]); + entry.* = .{ + .prev_hash_chain_root = 0, + .magic = 0, + .command = 0, + .size = 64 + 128, + }; + entry.set_checksum_and_hash_chain_root(buffer[0..entry.size]); + try std.io.null_writer.print("{}\n", .{entry}); +} diff --git a/test/behavior/bugs/679.zig b/test/behavior/bugs/679.zig new file mode 100644 index 0000000000..20b2b7b02a --- /dev/null +++ b/test/behavior/bugs/679.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const expect = std.testing.expect; + +pub fn List(comptime T: type) type { + return u32; +} + +const ElementList = List(Element); +const Element = struct { + link: ElementList, +}; + +test "false dependency loop in struct definition" { + const listType = ElementList; + var x: listType = 42; + expect(x == 42); +} diff --git a/test/behavior/bugs/6850.zig b/test/behavior/bugs/6850.zig new file mode 100644 index 0000000000..6759ba79ca --- /dev/null +++ b/test/behavior/bugs/6850.zig @@ -0,0 +1,12 @@ +const std = @import("std"); + +test "lazy sizeof comparison with zero" { + const Empty = struct {}; + const T = *Empty; + + std.testing.expect(hasNoBits(T)); +} + +fn hasNoBits(comptime T: type) bool { + return @sizeOf(T) == 0; +} diff --git a/test/behavior/bugs/7003.zig b/test/behavior/bugs/7003.zig new file mode 100644 index 0000000000..75fc9068b1 --- /dev/null +++ b/test/behavior/bugs/7003.zig @@ -0,0 +1,8 @@ +test "@Type should resolve its children types" { + const sparse = enum(u2) { a, b, c }; + const dense = enum(u2) { a, b, c, d }; + + comptime var sparse_info = @typeInfo(anyerror!sparse); + sparse_info.ErrorUnion.payload = dense; + const B = @Type(sparse_info); +} diff --git a/test/behavior/bugs/7027.zig b/test/behavior/bugs/7027.zig new file mode 100644 index 0000000000..782eebbfc9 --- /dev/null +++ b/test/behavior/bugs/7027.zig @@ -0,0 +1,17 @@ +const Foobar = struct { + myTypes: [128]type, + str: [1024]u8, + + fn foo() @This() { + comptime var foobar: Foobar = undefined; + foobar.str = [_]u8{'a'} ** 1024; + return foobar; + } +}; + +fn foo(arg: anytype) void {} + +test "" { + comptime var foobar = Foobar.foo(); + foo(foobar.str[0..10]); +} diff --git a/test/behavior/bugs/704.zig b/test/behavior/bugs/704.zig new file mode 100644 index 0000000000..765336c95f --- /dev/null +++ b/test/behavior/bugs/704.zig @@ -0,0 +1,7 @@ +const xxx = struct { + pub fn bar(self: *xxx) void {} +}; +test "bug 704" { + var x: xxx = undefined; + x.bar(); +} diff --git a/test/behavior/bugs/7047.zig b/test/behavior/bugs/7047.zig new file mode 100644 index 0000000000..0704e97b48 --- /dev/null +++ b/test/behavior/bugs/7047.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +const U = union(enum) { + T: type, + N: void, +}; + +fn S(comptime query: U) type { + return struct { + fn tag() type { + return query.T; + } + }; +} + +test "compiler doesn't consider equal unions with different 'type' payload" { + const s1 = S(U{ .T = u32 }).tag(); + std.testing.expectEqual(u32, s1); + + const s2 = S(U{ .T = u64 }).tag(); + std.testing.expectEqual(u64, s2); +} diff --git a/test/behavior/bugs/718.zig b/test/behavior/bugs/718.zig new file mode 100644 index 0000000000..b5a57b8944 --- /dev/null +++ b/test/behavior/bugs/718.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const mem = std.mem; +const expect = std.testing.expect; +const Keys = struct { + up: bool, + down: bool, + left: bool, + right: bool, +}; +var keys: Keys = undefined; +test "zero keys with @memset" { + @memset(@ptrCast([*]u8, &keys), 0, @sizeOf(@TypeOf(keys))); + expect(!keys.up); + expect(!keys.down); + expect(!keys.left); + expect(!keys.right); +} diff --git a/test/behavior/bugs/7250.zig b/test/behavior/bugs/7250.zig new file mode 100644 index 0000000000..b0f1aa15df --- /dev/null +++ b/test/behavior/bugs/7250.zig @@ -0,0 +1,15 @@ +const nrfx_uart_t = extern struct { + p_reg: [*c]u32, + drv_inst_idx: u8, +}; + +pub fn nrfx_uart_rx(p_instance: [*c]const nrfx_uart_t) void {} + +threadlocal var g_uart0 = nrfx_uart_t{ + .p_reg = 0, + .drv_inst_idx = 0, +}; + +test "reference a global threadlocal variable" { + _ = nrfx_uart_rx(&g_uart0); +} diff --git a/test/behavior/bugs/726.zig b/test/behavior/bugs/726.zig new file mode 100644 index 0000000000..632d3b1511 --- /dev/null +++ b/test/behavior/bugs/726.zig @@ -0,0 +1,15 @@ +const expect = @import("std").testing.expect; + +test "@ptrCast from const to nullable" { + const c: u8 = 4; + var x: ?*const u8 = @ptrCast(?*const u8, &c); + expect(x.?.* == 4); +} + +test "@ptrCast from var in empty struct to nullable" { + const container = struct { + var c: u8 = 4; + }; + var x: ?*const u8 = @ptrCast(?*const u8, &container.c); + expect(x.?.* == 4); +} diff --git a/test/behavior/bugs/828.zig b/test/behavior/bugs/828.zig new file mode 100644 index 0000000000..50ae0fd279 --- /dev/null +++ b/test/behavior/bugs/828.zig @@ -0,0 +1,33 @@ +const CountBy = struct { + a: usize, + + const One = CountBy{ .a = 1 }; + + pub fn counter(self: *const CountBy) Counter { + return Counter{ .i = 0 }; + } +}; + +const Counter = struct { + i: usize, + + pub fn count(self: *Counter) bool { + self.i += 1; + return self.i <= 10; + } +}; + +fn constCount(comptime cb: *const CountBy, comptime unused: u32) void { + comptime { + var cnt = cb.counter(); + if (cnt.i != 0) @compileError("Counter instance reused!"); + while (cnt.count()) {} + } +} + +test "comptime struct return should not return the same instance" { + //the first parameter must be passed by reference to trigger the bug + //a second parameter is required to trigger the bug + const ValA = constCount(&CountBy.One, 12); + const ValB = constCount(&CountBy.One, 15); +} diff --git a/test/behavior/bugs/920.zig b/test/behavior/bugs/920.zig new file mode 100644 index 0000000000..72854956a1 --- /dev/null +++ b/test/behavior/bugs/920.zig @@ -0,0 +1,65 @@ +const std = @import("std"); +const math = std.math; +const Random = std.rand.Random; + +const ZigTable = struct { + r: f64, + x: [257]f64, + f: [257]f64, + + pdf: fn (f64) f64, + is_symmetric: bool, + zero_case: fn (*Random, f64) f64, +}; + +fn ZigTableGen(comptime is_symmetric: bool, comptime r: f64, comptime v: f64, comptime f: fn (f64) f64, comptime f_inv: fn (f64) f64, comptime zero_case: fn (*Random, f64) f64) ZigTable { + var tables: ZigTable = undefined; + + tables.is_symmetric = is_symmetric; + tables.r = r; + tables.pdf = f; + tables.zero_case = zero_case; + + tables.x[0] = v / f(r); + tables.x[1] = r; + + for (tables.x[2..256]) |*entry, i| { + const last = tables.x[2 + i - 1]; + entry.* = f_inv(v / last + f(last)); + } + tables.x[256] = 0; + + for (tables.f[0..]) |*entry, i| { + entry.* = f(tables.x[i]); + } + + return tables; +} + +const norm_r = 3.6541528853610088; +const norm_v = 0.00492867323399; + +fn norm_f(x: f64) f64 { + return math.exp(-x * x / 2.0); +} +fn norm_f_inv(y: f64) f64 { + return math.sqrt(-2.0 * math.ln(y)); +} +fn norm_zero_case(random: *Random, u: f64) f64 { + return 0.0; +} + +const NormalDist = blk: { + @setEvalBranchQuota(30000); + break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case); +}; + +test "bug 920 fixed" { + const NormalDist1 = blk: { + break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case); + }; + + for (NormalDist1.f) |_, i| { + std.testing.expectEqual(NormalDist1.f[i], NormalDist.f[i]); + } +} diff --git a/test/behavior/byteswap.zig b/test/behavior/byteswap.zig new file mode 100644 index 0000000000..c6d658a8a2 --- /dev/null +++ b/test/behavior/byteswap.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "@byteSwap integers" { + const ByteSwapIntTest = struct { + fn run() void { + t(u0, 0, 0); + t(u8, 0x12, 0x12); + t(u16, 0x1234, 0x3412); + t(u24, 0x123456, 0x563412); + t(u32, 0x12345678, 0x78563412); + t(u40, 0x123456789a, 0x9a78563412); + t(i48, 0x123456789abc, @bitCast(i48, @as(u48, 0xbc9a78563412))); + t(u56, 0x123456789abcde, 0xdebc9a78563412); + t(u64, 0x123456789abcdef1, 0xf1debc9a78563412); + t(u128, 0x123456789abcdef11121314151617181, 0x8171615141312111f1debc9a78563412); + + t(u0, @as(u0, 0), 0); + t(i8, @as(i8, -50), -50); + t(i16, @bitCast(i16, @as(u16, 0x1234)), @bitCast(i16, @as(u16, 0x3412))); + t(i24, @bitCast(i24, @as(u24, 0x123456)), @bitCast(i24, @as(u24, 0x563412))); + t(i32, @bitCast(i32, @as(u32, 0x12345678)), @bitCast(i32, @as(u32, 0x78563412))); + t(u40, @bitCast(i40, @as(u40, 0x123456789a)), @as(u40, 0x9a78563412)); + t(i48, @bitCast(i48, @as(u48, 0x123456789abc)), @bitCast(i48, @as(u48, 0xbc9a78563412))); + t(i56, @bitCast(i56, @as(u56, 0x123456789abcde)), @bitCast(i56, @as(u56, 0xdebc9a78563412))); + t(i64, @bitCast(i64, @as(u64, 0x123456789abcdef1)), @bitCast(i64, @as(u64, 0xf1debc9a78563412))); + t( + i128, + @bitCast(i128, @as(u128, 0x123456789abcdef11121314151617181)), + @bitCast(i128, @as(u128, 0x8171615141312111f1debc9a78563412)), + ); + } + fn t(comptime I: type, input: I, expected_output: I) void { + std.testing.expectEqual(expected_output, @byteSwap(I, input)); + } + }; + comptime ByteSwapIntTest.run(); + ByteSwapIntTest.run(); +} + +test "@byteSwap vectors" { + // https://github.com/ziglang/zig/issues/3563 + if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest; + + // https://github.com/ziglang/zig/issues/3317 + if (std.Target.current.cpu.arch == .mipsel or std.Target.current.cpu.arch == .mips) return error.SkipZigTest; + + const ByteSwapVectorTest = struct { + fn run() void { + t(u8, 2, [_]u8{ 0x12, 0x13 }, [_]u8{ 0x12, 0x13 }); + t(u16, 2, [_]u16{ 0x1234, 0x2345 }, [_]u16{ 0x3412, 0x4523 }); + t(u24, 2, [_]u24{ 0x123456, 0x234567 }, [_]u24{ 0x563412, 0x674523 }); + } + + fn t( + comptime I: type, + comptime n: comptime_int, + input: std.meta.Vector(n, I), + expected_vector: std.meta.Vector(n, I), + ) void { + const actual_output: [n]I = @byteSwap(I, input); + const expected_output: [n]I = expected_vector; + std.testing.expectEqual(expected_output, actual_output); + } + }; + comptime ByteSwapVectorTest.run(); + ByteSwapVectorTest.run(); +} diff --git a/test/behavior/byval_arg_var.zig b/test/behavior/byval_arg_var.zig new file mode 100644 index 0000000000..ec3d18a532 --- /dev/null +++ b/test/behavior/byval_arg_var.zig @@ -0,0 +1,27 @@ +const std = @import("std"); + +var result: []const u8 = "wrong"; + +test "pass string literal byvalue to a generic var param" { + start(); + blowUpStack(10); + + std.testing.expect(std.mem.eql(u8, result, "string literal")); +} + +fn start() void { + foo("string literal"); +} + +fn foo(x: anytype) void { + bar(x); +} + +fn bar(x: anytype) void { + result = x; +} + +fn blowUpStack(x: u32) void { + if (x == 0) return; + blowUpStack(x - 1); +} diff --git a/test/behavior/call.zig b/test/behavior/call.zig new file mode 100644 index 0000000000..4d05a83a39 --- /dev/null +++ b/test/behavior/call.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; + expect(@call(.{}, foo, .{}) == 1234); + comptime { + // modifiers that allow comptime calls + expect(@call(.{}, foo, .{}) == 1234); + expect(@call(.{ .modifier = .no_async }, foo, .{}) == 1234); + expect(@call(.{ .modifier = .always_tail }, foo, .{}) == 1234); + expect(@call(.{ .modifier = .always_inline }, foo, .{}) == 1234); + } + { + // comptime call without comptime keyword + const result = @call(.{ .modifier = .compile_time }, foo, .{}) == 1234; + comptime expect(result); + } + { + // call of non comptime-known function + var alias_foo = foo; + expect(@call(.{ .modifier = .no_async }, alias_foo, .{}) == 1234); + expect(@call(.{ .modifier = .never_tail }, alias_foo, .{}) == 1234); + 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; + expect(@call(.{}, add, .{ a, 34 }) == 46); + expect(@call(.{}, add, .{ 12, b }) == 46); + expect(@call(.{}, add, .{ a, b }) == 46); + expect(@call(.{}, add, .{ 12, 34 }) == 46); + comptime 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 }; + expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46); + expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46); + expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46); + 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; + expectEqual(?i32, S.ReturnType(inst.call_me_maybe)); +} diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig new file mode 100644 index 0000000000..0852a151a9 --- /dev/null +++ b/test/behavior/cast.zig @@ -0,0 +1,927 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; +const maxInt = std.math.maxInt; +const Vector = std.meta.Vector; +const native_endian = @import("builtin").target.cpu.arch.endian(); + +test "int to ptr cast" { + const x = @as(usize, 13); + const y = @intToPtr(*u8, x); + const z = @ptrToInt(y); + expect(z == 13); +} + +test "integer literal to pointer cast" { + const vga_mem = @intToPtr(*u16, 0xB8000); + expect(@ptrToInt(vga_mem) == 0xB8000); +} + +test "pointer reinterpret const float to int" { + // The hex representation is 0x3fe3333333333303. + const float: f64 = 5.99999999999994648725e-01; + const float_ptr = &float; + const int_ptr = @ptrCast(*const i32, float_ptr); + const int_val = int_ptr.*; + if (native_endian == .Little) + expect(int_val == 0x33333303) + else + expect(int_val == 0x3fe33333); +} + +test "implicitly cast indirect pointer to maybe-indirect pointer" { + const S = struct { + const Self = @This(); + x: u8, + fn constConst(p: *const *const Self) u8 { + return p.*.x; + } + fn maybeConstConst(p: ?*const *const Self) u8 { + return p.?.*.x; + } + fn constConstConst(p: *const *const *const Self) u8 { + return p.*.*.x; + } + fn maybeConstConstConst(p: ?*const *const *const Self) u8 { + return p.?.*.*.x; + } + }; + const s = S{ .x = 42 }; + const p = &s; + const q = &p; + const r = &q; + expect(42 == S.constConst(q)); + expect(42 == S.maybeConstConst(q)); + expect(42 == S.constConstConst(r)); + expect(42 == S.maybeConstConstConst(r)); +} + +test "explicit cast from integer to error type" { + testCastIntToErr(error.ItBroke); + comptime testCastIntToErr(error.ItBroke); +} +fn testCastIntToErr(err: anyerror) void { + const x = @errorToInt(err); + const y = @intToError(x); + expect(error.ItBroke == y); +} + +test "peer resolve arrays of different size to const slice" { + expect(mem.eql(u8, boolToStr(true), "true")); + expect(mem.eql(u8, boolToStr(false), "false")); + comptime expect(mem.eql(u8, boolToStr(true), "true")); + comptime expect(mem.eql(u8, boolToStr(false), "false")); +} +fn boolToStr(b: bool) []const u8 { + return if (b) "true" else "false"; +} + +test "peer resolve array and const slice" { + testPeerResolveArrayConstSlice(true); + comptime testPeerResolveArrayConstSlice(true); +} +fn testPeerResolveArrayConstSlice(b: bool) void { + const value1 = if (b) "aoeu" else @as([]const u8, "zz"); + const value2 = if (b) @as([]const u8, "zz") else "aoeu"; + expect(mem.eql(u8, value1, "aoeu")); + expect(mem.eql(u8, value2, "zz")); +} + +test "implicitly cast from T to anyerror!?T" { + castToOptionalTypeError(1); + comptime castToOptionalTypeError(1); +} + +const A = struct { + a: i32, +}; +fn castToOptionalTypeError(z: i32) void { + const x = @as(i32, 1); + const y: anyerror!?i32 = x; + expect((try y).? == 1); + + const f = z; + const g: anyerror!?i32 = f; + + const a = A{ .a = z }; + const b: anyerror!?A = a; + expect((b catch unreachable).?.a == 1); +} + +test "implicitly cast from int to anyerror!?T" { + implicitIntLitToOptional(); + comptime implicitIntLitToOptional(); +} +fn implicitIntLitToOptional() void { + const f: ?i32 = 1; + const g: anyerror!?i32 = 1; +} + +test "return null from fn() anyerror!?&T" { + const a = returnNullFromOptionalTypeErrorRef(); + const b = returnNullLitFromOptionalTypeErrorRef(); + expect((try a) == null and (try b) == null); +} +fn returnNullFromOptionalTypeErrorRef() anyerror!?*A { + const a: ?*A = null; + return a; +} +fn returnNullLitFromOptionalTypeErrorRef() anyerror!?*A { + return null; +} + +test "peer type resolution: ?T and T" { + expect(peerTypeTAndOptionalT(true, false).? == 0); + expect(peerTypeTAndOptionalT(false, false).? == 3); + comptime { + expect(peerTypeTAndOptionalT(true, false).? == 0); + 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" { + expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0); + expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1); + comptime { + expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0); + expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1); + } +} +fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 { + if (a) { + return &[_]u8{}; + } + + return slice[0..1]; +} + +test "implicitly cast from [N]T to ?[]const T" { + expect(mem.eql(u8, castToOptionalSlice().?, "hi")); + comptime expect(mem.eql(u8, castToOptionalSlice().?, "hi")); +} + +fn castToOptionalSlice() ?[]const u8 { + return "hi"; +} + +test "implicitly cast from [0]T to anyerror![]T" { + testCastZeroArrayToErrSliceMut(); + comptime testCastZeroArrayToErrSliceMut(); +} + +fn testCastZeroArrayToErrSliceMut() void { + expect((gimmeErrOrSlice() catch unreachable).len == 0); +} + +fn gimmeErrOrSlice() anyerror![]u8 { + return &[_]u8{}; +} + +test "peer type resolution: [0]u8, []const u8, and anyerror![]u8" { + const S = struct { + fn doTheTest() anyerror!void { + { + var data = "hi".*; + const slice = data[0..]; + expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0); + expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1); + } + { + var data: [2]u8 = "hi".*; + const slice = data[0..]; + expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0); + expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1); + } + } + }; + try S.doTheTest(); + try comptime S.doTheTest(); +} +fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 { + if (a) { + return &[_]u8{}; + } + + return slice[0..1]; +} + +test "resolve undefined with integer" { + testResolveUndefWithInt(true, 1234); + comptime testResolveUndefWithInt(true, 1234); +} +fn testResolveUndefWithInt(b: bool, x: i32) void { + const value = if (b) x else undefined; + if (b) { + expect(value == x); + } +} + +test "implicit cast from &const [N]T to []const T" { + testCastConstArrayRefToConstSlice(); + comptime testCastConstArrayRefToConstSlice(); +} + +fn testCastConstArrayRefToConstSlice() void { + { + const blah = "aoeu".*; + const const_array_ref = &blah; + expect(@TypeOf(const_array_ref) == *const [4:0]u8); + const slice: []const u8 = const_array_ref; + expect(mem.eql(u8, slice, "aoeu")); + } + { + const blah: [4]u8 = "aoeu".*; + const const_array_ref = &blah; + expect(@TypeOf(const_array_ref) == *const [4]u8); + const slice: []const u8 = const_array_ref; + expect(mem.eql(u8, slice, "aoeu")); + } +} + +test "peer type resolution: error and [N]T" { + expect(mem.eql(u8, try testPeerErrorAndArray(0), "OK")); + comptime expect(mem.eql(u8, try testPeerErrorAndArray(0), "OK")); + expect(mem.eql(u8, try testPeerErrorAndArray2(1), "OKK")); + comptime expect(mem.eql(u8, try testPeerErrorAndArray2(1), "OKK")); +} + +fn testPeerErrorAndArray(x: u8) anyerror![]const u8 { + return switch (x) { + 0x00 => "OK", + else => error.BadValue, + }; +} +fn testPeerErrorAndArray2(x: u8) anyerror![]const u8 { + return switch (x) { + 0x00 => "OK", + 0x01 => "OKK", + else => error.BadValue, + }; +} + +test "@floatToInt" { + testFloatToInts(); + comptime testFloatToInts(); +} + +fn testFloatToInts() void { + const x = @as(i32, 1e4); + expect(x == 10000); + const y = @floatToInt(i32, @as(f32, 1e4)); + expect(y == 10000); + expectFloatToInt(f16, 255.1, u8, 255); + expectFloatToInt(f16, 127.2, i8, 127); + expectFloatToInt(f16, -128.2, i8, -128); + expectFloatToInt(f32, 255.1, u8, 255); + expectFloatToInt(f32, 127.2, i8, 127); + expectFloatToInt(f32, -128.2, i8, -128); + expectFloatToInt(comptime_int, 1234, i16, 1234); +} + +fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) void { + expect(@floatToInt(I, f) == i); +} + +test "cast u128 to f128 and back" { + comptime testCast128(); + testCast128(); +} + +fn testCast128() void { + expect(cast128Int(cast128Float(0x7fff0000000000000000000000000000)) == 0x7fff0000000000000000000000000000); +} + +fn cast128Int(x: f128) u128 { + return @bitCast(u128, x); +} + +fn cast128Float(x: u128) f128 { + return @bitCast(f128, x); +} + +test "single-item pointer of array to slice and to unknown length pointer" { + testCastPtrOfArrayToSliceAndPtr(); + comptime testCastPtrOfArrayToSliceAndPtr(); +} + +fn testCastPtrOfArrayToSliceAndPtr() void { + { + var array = "aoeu".*; + const x: [*]u8 = &array; + x[0] += 1; + expect(mem.eql(u8, array[0..], "boeu")); + const y: []u8 = &array; + y[0] += 1; + expect(mem.eql(u8, array[0..], "coeu")); + } + { + var array: [4]u8 = "aoeu".*; + const x: [*]u8 = &array; + x[0] += 1; + expect(mem.eql(u8, array[0..], "boeu")); + const y: []u8 = &array; + y[0] += 1; + expect(mem.eql(u8, array[0..], "coeu")); + } +} + +test "cast *[1][*]const u8 to [*]const ?[*]const u8" { + const window_name = [1][*]const u8{"window name"}; + const x: [*]const ?[*]const u8 = &window_name; + expect(mem.eql(u8, std.mem.spanZ(@ptrCast([*:0]const u8, x[0].?)), "window name")); +} + +test "@intCast comptime_int" { + const result = @intCast(i32, 1234); + expect(@TypeOf(result) == i32); + expect(result == 1234); +} + +test "@floatCast comptime_int and comptime_float" { + { + const result = @floatCast(f16, 1234); + expect(@TypeOf(result) == f16); + expect(result == 1234.0); + } + { + const result = @floatCast(f16, 1234.0); + expect(@TypeOf(result) == f16); + expect(result == 1234.0); + } + { + const result = @floatCast(f32, 1234); + expect(@TypeOf(result) == f32); + expect(result == 1234.0); + } + { + const result = @floatCast(f32, 1234.0); + expect(@TypeOf(result) == f32); + expect(result == 1234.0); + } +} + +test "vector casts" { + const S = struct { + fn doTheTest() void { + // Upcast (implicit, equivalent to @intCast) + var up0: Vector(2, u8) = [_]u8{ 0x55, 0xaa }; + var up1 = @as(Vector(2, u16), up0); + var up2 = @as(Vector(2, u32), up0); + var up3 = @as(Vector(2, u64), up0); + // Downcast (safety-checked) + var down0 = up3; + var down1 = @intCast(Vector(2, u32), down0); + var down2 = @intCast(Vector(2, u16), down0); + var down3 = @intCast(Vector(2, u8), down0); + + expect(mem.eql(u16, &@as([2]u16, up1), &[2]u16{ 0x55, 0xaa })); + expect(mem.eql(u32, &@as([2]u32, up2), &[2]u32{ 0x55, 0xaa })); + expect(mem.eql(u64, &@as([2]u64, up3), &[2]u64{ 0x55, 0xaa })); + + expect(mem.eql(u32, &@as([2]u32, down1), &[2]u32{ 0x55, 0xaa })); + expect(mem.eql(u16, &@as([2]u16, down2), &[2]u16{ 0x55, 0xaa })); + expect(mem.eql(u8, &@as([2]u8, down3), &[2]u8{ 0x55, 0xaa })); + } + + fn doTheTestFloat() void { + var vec = @splat(2, @as(f32, 1234.0)); + var wider: Vector(2, f64) = vec; + expect(wider[0] == 1234.0); + expect(wider[1] == 1234.0); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); + S.doTheTestFloat(); + comptime S.doTheTestFloat(); +} + +test "comptime_int @intToFloat" { + { + const result = @intToFloat(f16, 1234); + expect(@TypeOf(result) == f16); + expect(result == 1234.0); + } + { + const result = @intToFloat(f32, 1234); + expect(@TypeOf(result) == f32); + expect(result == 1234.0); + } + { + const result = @intToFloat(f64, 1234); + expect(@TypeOf(result) == f64); + expect(result == 1234.0); + } + { + const result = @intToFloat(f128, 1234); + expect(@TypeOf(result) == f128); + expect(result == 1234.0); + } + // big comptime_int (> 64 bits) to f128 conversion + { + const result = @intToFloat(f128, 0x1_0000_0000_0000_0000); + expect(@TypeOf(result) == f128); + expect(result == 0x1_0000_0000_0000_0000.0); + } +} + +test "@intCast i32 to u7" { + var x: u128 = maxInt(u128); + var y: i32 = 120; + var z = x >> @intCast(u7, y); + expect(z == 0xff); +} + +test "@floatCast cast down" { + { + var double: f64 = 0.001534; + var single = @floatCast(f32, double); + expect(single == 0.001534); + } + { + const double: f64 = 0.001534; + const single = @floatCast(f32, double); + expect(single == 0.001534); + } +} + +test "implicit cast undefined to optional" { + expect(MakeType(void).getNull() == null); + expect(MakeType(void).getNonNull() != null); +} + +fn MakeType(comptime T: type) type { + return struct { + fn getNull() ?T { + return null; + } + + fn getNonNull() ?T { + return @as(T, undefined); + } + }; +} + +test "implicit cast from *[N]T to ?[*]T" { + var x: ?[*]u16 = null; + var y: [4]u16 = [4]u16{ 0, 1, 2, 3 }; + + x = &y; + expect(std.mem.eql(u16, x.?[0..4], y[0..4])); + x.?[0] = 8; + y[3] = 6; + expect(std.mem.eql(u16, x.?[0..4], y[0..4])); +} + +test "implicit cast from *[N]T to [*c]T" { + var x: [4]u16 = [4]u16{ 0, 1, 2, 3 }; + var y: [*c]u16 = &x; + + expect(std.mem.eql(u16, x[0..4], y[0..4])); + x[0] = 8; + y[3] = 6; + expect(std.mem.eql(u16, x[0..4], y[0..4])); +} + +test "implicit cast from *T to ?*c_void" { + var a: u8 = 1; + incrementVoidPtrValue(&a); + std.testing.expect(a == 2); +} + +fn incrementVoidPtrValue(value: ?*c_void) void { + @ptrCast(*u8, value.?).* += 1; +} + +test "implicit cast from [*]T to ?*c_void" { + var a = [_]u8{ 3, 2, 1 }; + var runtime_zero: usize = 0; + incrementVoidPtrArray(a[runtime_zero..].ptr, 3); + expect(std.mem.eql(u8, &a, &[_]u8{ 4, 3, 2 })); +} + +fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { + var n: usize = 0; + while (n < len) : (n += 1) { + @ptrCast([*]u8, array.?)[n] += 1; + } +} + +test "*usize to *void" { + var i = @as(usize, 0); + var v = @ptrCast(*void, &i); + v.* = {}; +} + +test "compile time int to ptr of function" { + foobar(FUNCTION_CONSTANT); +} + +pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize)); +pub const PFN_void = fn (*c_void) callconv(.C) void; + +fn foobar(func: PFN_void) void { + std.testing.expect(@ptrToInt(func) == maxInt(usize)); +} + +test "implicit ptr to *c_void" { + var a: u32 = 1; + var ptr: *align(@alignOf(u32)) c_void = &a; + var b: *u32 = @ptrCast(*u32, ptr); + expect(b.* == 1); + var ptr2: ?*align(@alignOf(u32)) c_void = &a; + var c: *u32 = @ptrCast(*u32, ptr2.?); + expect(c.* == 1); +} + +test "@intCast to comptime_int" { + 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; + expect(b == 255); +} + +test "@intToEnum passed a comptime_int to an enum with one item" { + const E = enum { + A, + }; + const x = @intToEnum(E, 0); + expect(x == E.A); +} + +test "@intToEnum runtime to an extern enum with duplicate values" { + const E = extern enum(u8) { + A = 1, + B = 1, + }; + var a: u8 = 1; + var x = @intToEnum(E, a); + expect(x == E.A); + expect(x == E.B); +} + +test "@intCast to u0 and use the result" { + const S = struct { + fn doTheTest(zero: u1, one: u1, bigzero: i32) void { + expect((one << @intCast(u0, bigzero)) == 1); + expect((zero << @intCast(u0, bigzero)) == 0); + } + }; + S.doTheTest(0, 1, 0); + comptime S.doTheTest(0, 1, 0); +} + +test "peer type resolution: unreachable, null, slice" { + const S = struct { + fn doTheTest(num: usize, word: []const u8) void { + const result = switch (num) { + 0 => null, + 1 => word, + else => unreachable, + }; + expect(mem.eql(u8, result.?, "hi")); + } + }; + S.doTheTest(1, "hi"); +} + +test "peer type resolution: unreachable, error set, unreachable" { + const Error = error{ + FileDescriptorAlreadyPresentInSet, + OperationCausesCircularLoop, + FileDescriptorNotRegistered, + SystemResources, + UserResourceLimitReached, + FileDescriptorIncompatibleWithEpoll, + Unexpected, + }; + var err = Error.SystemResources; + const transformed_err = switch (err) { + error.FileDescriptorAlreadyPresentInSet => unreachable, + error.OperationCausesCircularLoop => unreachable, + error.FileDescriptorNotRegistered => unreachable, + error.SystemResources => error.SystemResources, + error.UserResourceLimitReached => error.UserResourceLimitReached, + error.FileDescriptorIncompatibleWithEpoll => unreachable, + error.Unexpected => unreachable, + }; + expect(transformed_err == error.SystemResources); +} + +test "implicit cast comptime_int to comptime_float" { + comptime expect(@as(comptime_float, 10) == @as(f32, 10)); + expect(2 == 2.0); +} + +test "implicit cast *[0]T to E![]const u8" { + var x = @as(anyerror![]const u8, &[0]u8{}); + expect((x catch unreachable).len == 0); +} + +test "peer cast *[0]T to E![]const T" { + var buffer: [5]u8 = "abcde".*; + var buf: anyerror![]const u8 = buffer[0..]; + var b = false; + var y = if (b) &[0]u8{} else buf; + expect(mem.eql(u8, "abcde", y catch unreachable)); +} + +test "peer cast *[0]T to []const T" { + var buffer: [5]u8 = "abcde".*; + var buf: []const u8 = buffer[0..]; + var b = false; + var y = if (b) &[0]u8{} else buf; + expect(mem.eql(u8, "abcde", y)); +} + +var global_array: [4]u8 = undefined; +test "cast from array reference to fn" { + const f = @ptrCast(fn () callconv(.C) void, &global_array); + expect(@ptrToInt(f) == @ptrToInt(&global_array)); +} + +test "*const [N]null u8 to ?[]const u8" { + const S = struct { + fn doTheTest() void { + var a = "Hello"; + var b: ?[]const u8 = a; + expect(mem.eql(u8, b.?, "Hello")); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "peer resolution of string literals" { + const S = struct { + const E = extern enum { + a, + b, + c, + d, + }; + + fn doTheTest(e: E) void { + const cmd = switch (e) { + .a => "one", + .b => "two", + .c => "three", + .d => "four", + }; + expect(mem.eql(u8, cmd, "two")); + } + }; + S.doTheTest(.b); + comptime S.doTheTest(.b); +} + +test "type coercion related to sentinel-termination" { + const S = struct { + fn doTheTest() void { + // [:x]T to []T + { + var array = [4:0]i32{ 1, 2, 3, 4 }; + var slice: [:0]i32 = &array; + var dest: []i32 = slice; + expect(mem.eql(i32, dest, &[_]i32{ 1, 2, 3, 4 })); + } + + // [*:x]T to [*]T + { + var array = [4:99]i32{ 1, 2, 3, 4 }; + var dest: [*]i32 = &array; + expect(dest[0] == 1); + expect(dest[1] == 2); + expect(dest[2] == 3); + expect(dest[3] == 4); + expect(dest[4] == 99); + } + + // [N:x]T to [N]T + { + var array = [4:0]i32{ 1, 2, 3, 4 }; + var dest: [4]i32 = array; + expect(mem.eql(i32, &dest, &[_]i32{ 1, 2, 3, 4 })); + } + + // *[N:x]T to *[N]T + { + var array = [4:0]i32{ 1, 2, 3, 4 }; + var dest: *[4]i32 = &array; + expect(mem.eql(i32, dest, &[_]i32{ 1, 2, 3, 4 })); + } + + // [:x]T to [*:x]T + { + var array = [4:0]i32{ 1, 2, 3, 4 }; + var slice: [:0]i32 = &array; + var dest: [*:0]i32 = slice; + expect(dest[0] == 1); + expect(dest[1] == 2); + expect(dest[2] == 3); + expect(dest[3] == 4); + expect(dest[4] == 0); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "cast i8 fn call peers to i32 result" { + const S = struct { + fn doTheTest() void { + var cond = true; + const value: i32 = if (cond) smallBoi() else bigBoi(); + expect(value == 123); + } + fn smallBoi() i8 { + return 123; + } + fn bigBoi() i16 { + return 1234; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "return u8 coercing into ?u32 return type" { + const S = struct { + fn doTheTest() void { + expect(foo(123).? == 123); + } + fn foo(arg: u8) ?u32 { + return arg; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "peer result null and comptime_int" { + const S = struct { + fn blah(n: i32) ?i32 { + if (n == 0) { + return null; + } else if (n < 0) { + return -1; + } else { + return 1; + } + } + }; + + expect(S.blah(0) == null); + comptime expect(S.blah(0) == null); + expect(S.blah(10).? == 1); + comptime expect(S.blah(10).? == 1); + expect(S.blah(-10).? == -1); + comptime expect(S.blah(-10).? == -1); +} + +test "peer type resolution implicit cast to return type" { + const S = struct { + fn doTheTest() void { + for ("hello") |c| _ = f(c); + } + fn f(c: u8) []const u8 { + return switch (c) { + 'h', 'e' => &[_]u8{c}, // should cast to slice + 'l', ' ' => &[_]u8{ c, '.' }, // should cast to slice + else => ([_]u8{c})[0..], // is a slice + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "peer type resolution implicit cast to variable type" { + const S = struct { + fn doTheTest() void { + var x: []const u8 = undefined; + for ("hello") |c| x = switch (c) { + 'h', 'e' => &[_]u8{c}, // should cast to slice + 'l', ' ' => &[_]u8{ c, '.' }, // should cast to slice + else => ([_]u8{c})[0..], // is a slice + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "variable initialization uses result locations properly with regards to the type" { + var b = true; + const x: i32 = if (b) 1 else 2; + expect(x == 1); +} + +test "cast between [*c]T and ?[*:0]T on fn parameter" { + const S = struct { + const Handler = ?fn ([*c]const u8) callconv(.C) void; + fn addCallback(handler: Handler) void {} + + fn myCallback(cstr: ?[*:0]const u8) callconv(.C) void {} + + fn doTheTest() void { + addCallback(myCallback); + } + }; + S.doTheTest(); +} + +test "cast between C pointer with different but compatible types" { + const S = struct { + fn foo(arg: [*]c_ushort) u16 { + return arg[0]; + } + fn doTheTest() void { + var x = [_]u16{ 4, 2, 1, 3 }; + expect(foo(@ptrCast([*]u16, &x)) == 4); + } + }; + S.doTheTest(); +} + +var global_struct: struct { f0: usize } = undefined; + +test "assignment to optional pointer result loc" { + var foo: struct { ptr: ?*c_void } = .{ .ptr = &global_struct }; + expect(foo.ptr.? == @ptrCast(*c_void, &global_struct)); +} + +test "peer type resolve string lit with sentinel-terminated mutable slice" { + var array: [4:0]u8 = undefined; + array[4] = 0; // TODO remove this when #4372 is solved + var slice: [:0]u8 = array[0..4 :0]; + comptime expect(@TypeOf(slice, "hi") == [:0]const u8); + comptime expect(@TypeOf("hi", slice) == [:0]const u8); +} + +test "peer type unsigned int to signed" { + var w: u31 = 5; + var x: u8 = 7; + var y: i32 = -5; + var a = w + y + x; + comptime expect(@TypeOf(a) == i32); + expect(a == 7); +} + +test "peer type resolve array pointers, one of them const" { + var array1: [4]u8 = undefined; + const array2: [5]u8 = undefined; + comptime expect(@TypeOf(&array1, &array2) == []const u8); + comptime expect(@TypeOf(&array2, &array1) == []const u8); +} + +test "peer type resolve array pointer and unknown pointer" { + const const_array: [4]u8 = undefined; + var array: [4]u8 = undefined; + var const_ptr: [*]const u8 = undefined; + var ptr: [*]u8 = undefined; + + comptime expect(@TypeOf(&array, ptr) == [*]u8); + comptime expect(@TypeOf(ptr, &array) == [*]u8); + + comptime expect(@TypeOf(&const_array, ptr) == [*]const u8); + comptime expect(@TypeOf(ptr, &const_array) == [*]const u8); + + comptime expect(@TypeOf(&array, const_ptr) == [*]const u8); + comptime expect(@TypeOf(const_ptr, &array) == [*]const u8); + + comptime expect(@TypeOf(&const_array, const_ptr) == [*]const u8); + comptime expect(@TypeOf(const_ptr, &const_array) == [*]const u8); +} + +test "comptime float casts" { + const a = @intToFloat(comptime_float, 1); + expect(a == 1); + expect(@TypeOf(a) == comptime_float); + const b = @floatToInt(comptime_int, 2); + expect(b == 2); + expect(@TypeOf(b) == comptime_int); +} + +test "cast from ?[*]T to ??[*]T" { + const a: ??[*]u8 = @as(?[*]u8, null); + expect(a != null and a.? == null); +} + +test "cast between *[N]void and []void" { + var a: [4]void = undefined; + var b: []void = &a; + expect(b.len == 4); +} diff --git a/test/behavior/const_slice_child.zig b/test/behavior/const_slice_child.zig new file mode 100644 index 0000000000..92e5121026 --- /dev/null +++ b/test/behavior/const_slice_child.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const debug = std.debug; +const testing = std.testing; +const expect = testing.expect; + +var argv: [*]const [*]const u8 = undefined; + +test "const slice child" { + const strs = [_][*]const u8{ + "one", + "two", + "three", + }; + argv = &strs; + bar(strs.len); +} + +fn foo(args: [][]const u8) void { + expect(args.len == 3); + expect(streql(args[0], "one")); + expect(streql(args[1], "two")); + expect(streql(args[2], "three")); +} + +fn bar(argc: usize) void { + const args = testing.allocator.alloc([]const u8, argc) catch unreachable; + defer testing.allocator.free(args); + for (args) |_, i| { + const ptr = argv[i]; + args[i] = ptr[0..strlen(ptr)]; + } + foo(args); +} + +fn strlen(ptr: [*]const u8) usize { + var count: usize = 0; + while (ptr[count] != 0) : (count += 1) {} + return count; +} + +fn streql(a: []const u8, b: []const u8) bool { + if (a.len != b.len) return false; + for (a) |item, index| { + if (b[index] != item) return false; + } + return true; +} diff --git a/test/behavior/defer.zig b/test/behavior/defer.zig new file mode 100644 index 0000000000..6bfeb485cc --- /dev/null +++ b/test/behavior/defer.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" { + expect(runSomeErrorDefers(true) catch unreachable); + expect(result[0] == 'c'); + expect(result[1] == 'a'); + + const ok = runSomeErrorDefers(false) catch |err| x: { + expect(err == error.FalseNotAllowed); + break :x true; + }; + expect(ok); + expect(result[0] == 'c'); + expect(result[1] == 'b'); + 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); + } +} + +test "defer and labeled break" { + var i = @as(usize, 0); + + blk: { + defer i += 1; + break :blk; + } + + expect(i == 1); +} + +test "errdefer does not apply to fn inside fn" { + if (testNestedFnErrDefer()) |_| @panic("expected error") else |e| 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 { + expect(notNull().? == 1); + } + + fn notNull() ?u8 { + var res: ?u8 = 1; + defer res = null; + return res; + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "errdefer with payload" { + const S = struct { + fn foo() !i32 { + errdefer |a| { + expectEqual(error.One, a); + } + return error.One; + } + fn doTheTest() void { + expectError(error.One, foo()); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig new file mode 100644 index 0000000000..ecb95be8f5 --- /dev/null +++ b/test/behavior/enum.zig @@ -0,0 +1,1204 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; +const Tag = @import("std").meta.Tag; + +test "extern enum" { + const S = struct { + const i = extern enum { + n = 0, + o = 2, + p = 4, + q = 4, + }; + fn doTheTest(y: c_int) void { + var x = i.o; + switch (x) { + .n, .p => unreachable, + .o => {}, + } + } + }; + S.doTheTest(52); + comptime S.doTheTest(52); +} + +test "non-exhaustive enum" { + const S = struct { + const E = enum(u8) { + a, + b, + _, + }; + fn doTheTest(y: u8) void { + var e: E = .b; + expect(switch (e) { + .a => false, + .b => true, + _ => false, + }); + e = @intToEnum(E, 12); + expect(switch (e) { + .a => false, + .b => false, + _ => true, + }); + + expect(switch (e) { + .a => false, + .b => false, + else => true, + }); + e = .b; + expect(switch (e) { + .a => false, + else => true, + }); + + expect(@typeInfo(E).Enum.fields.len == 2); + e = @intToEnum(E, 12); + expect(@enumToInt(e) == 12); + e = @intToEnum(E, y); + expect(@enumToInt(e) == 52); + expect(@typeInfo(E).Enum.is_exhaustive == false); + } + }; + S.doTheTest(52); + comptime 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); + expect(switch (e) { + _ => true, + }); + expect(@enumToInt(e) == y); + + expect(@typeInfo(E).Enum.fields.len == 0); + expect(@typeInfo(E).Enum.is_exhaustive == false); + } + }; + S.doTheTest(42); + comptime 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; + expect(switch (e) { + .a => true, + _ => false, + }); + e = @intToEnum(E, 12); + expect(switch (e) { + .a => false, + _ => true, + }); + + expect(switch (e) { + .a => false, + else => true, + }); + e = .a; + expect(switch (e) { + .a => true, + else => false, + }); + + expect(@enumToInt(@intToEnum(E, y)) == y); + expect(@typeInfo(E).Enum.fields.len == 1); + expect(@typeInfo(E).Enum.is_exhaustive == false); + } + }; + S.doTheTest(23); + comptime S.doTheTest(23); +} + +test "enum type" { + const foo1 = Foo{ .One = 13 }; + const foo2 = Foo{ + .Two = Point{ + .x = 1234, + .y = 5678, + }, + }; + const bar = Bar.B; + + expect(bar == Bar.B); + expect(@typeInfo(Foo).Union.fields.len == 3); + expect(@typeInfo(Bar).Enum.fields.len == 4); + expect(@sizeOf(Foo) == @sizeOf(FooNoVoid)); + expect(@sizeOf(Bar) == 1); +} + +test "enum as return value" { + switch (returnAnInt(13)) { + Foo.One => |value| 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, +}; + +test "enum to int" { + shouldEqual(Number.Zero, 0); + shouldEqual(Number.One, 1); + shouldEqual(Number.Two, 2); + shouldEqual(Number.Three, 3); + shouldEqual(Number.Four, 4); +} + +fn shouldEqual(n: Number, expected: u3) void { + expect(@enumToInt(n) == expected); +} + +test "int to enum" { + testIntToEnumEval(3); +} +fn testIntToEnumEval(x: i32) void { + expect(@intToEnum(IntToEnumNumber, @intCast(u3, x)) == IntToEnumNumber.Three); +} +const IntToEnumNumber = enum { + Zero, + One, + Two, + Three, + Four, +}; + +test "@tagName" { + expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three")); + comptime expect(mem.eql(u8, testEnumTagNameBare(BareNumber.Three), "Three")); +} + +test "@tagName extern enum with duplicates" { + expect(mem.eql(u8, testEnumTagNameBare(ExternDuplicates.B), "A")); + comptime expect(mem.eql(u8, testEnumTagNameBare(ExternDuplicates.B), "A")); +} + +test "@tagName non-exhaustive enum" { + expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B")); + comptime expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B")); +} + +fn testEnumTagNameBare(n: anytype) []const u8 { + return @tagName(n); +} + +const BareNumber = enum { + One, + Two, + Three, +}; + +const ExternDuplicates = extern enum(u8) { + A = 1, + B = 1, +}; + +const NonExhaustive = enum(u8) { + A, + B, + _, +}; + +test "enum alignment" { + comptime { + expect(@alignOf(AlignTestEnum) >= @alignOf([9]u8)); + 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, +}; + +test "enum sizes" { + comptime { + expect(@sizeOf(ValueCount1) == 0); + expect(@sizeOf(ValueCount2) == 1); + expect(@sizeOf(ValueCount256) == 1); + expect(@sizeOf(ValueCount257) == 2); + } +} + +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 expect(Tag(Small) == u2); + } + { + var x = Small2.One; + x = Small2.Two; + comptime 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; + expect(getA(&data) == A.Two); + expect(getB(&data) == B.Three3); + expect(getC(&data) == C.Four4); + comptime expect(@sizeOf(BitFieldOfEnums) == 1); + + data.b = B.Four3; + expect(data.b == B.Four3); + + data.a = A.Three; + expect(data.a == A.Three); + 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" { + testCastEnumTag(Small2.Two); + comptime testCastEnumTag(Small2.Two); +} + +fn testCastEnumTag(value: Small2) void { + expect(@enumToInt(value) == 1); +} + +const MultipleChoice = enum(u32) { + A = 20, + B = 40, + C = 60, + D = 1000, +}; + +test "enum with specified tag values" { + testEnumWithSpecifiedTagValues(MultipleChoice.C); + comptime testEnumWithSpecifiedTagValues(MultipleChoice.C); +} + +fn testEnumWithSpecifiedTagValues(x: MultipleChoice) void { + expect(@enumToInt(x) == 60); + 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" { + testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D); + comptime testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2.D); +} + +fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void { + expect(@enumToInt(x) == 1000); + 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" { + expect(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1); + 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" { + 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 @import("std").testing.expect(@sizeOf(Enum) == @sizeOf(u8)); +} + +test "empty extern enum with members" { + const E = extern enum { + A, + B, + C, + }; + expect(@sizeOf(E) == @sizeOf(c_int)); +} + +test "tag name with assigned enum values" { + const LocalFoo = enum { + A = 1, + B = 0, + }; + var b = LocalFoo.B; + expect(mem.eql(u8, @tagName(b), "B")); +} + +test "enum literal equality" { + const x = .hi; + const y = .ok; + const z = .hi; + + expect(x != y); + expect(x == z); +} + +test "enum literal cast to enum" { + const Color = enum { + Auto, + Off, + On, + }; + + var color1: Color = .Auto; + var color2 = Color.Auto; + expect(color1 == color2); +} + +test "peer type resolution with enum literal" { + const Items = enum { + one, + two, + }; + + expect(Items.two == .two); + expect(.two == Items.two); +} + +test "enum literal in array literal" { + const Items = enum { + one, + two, + }; + + const array = [_]Items{ + .one, + .two, + }; + + expect(array[0] == .one); + expect(array[1] == .two); +} + +test "signed integer as enum tag" { + const SignedEnum = enum(i2) { + A0 = -1, + A1 = 0, + A2 = 1, + }; + + expect(@enumToInt(SignedEnum.A0) == -1); + expect(@enumToInt(SignedEnum.A1) == 0); + expect(@enumToInt(SignedEnum.A2) == 1); +} + +test "enum value allocation" { + const LargeEnum = enum(u32) { + A0 = 0x80000000, + A1, + A2, + }; + + expect(@enumToInt(LargeEnum.A0) == 0x80000000); + expect(@enumToInt(LargeEnum.A1) == 0x80000001); + 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, + }; + expect(@enumToInt(E.One) == 0); + const E2 = enum(u2) { + One = 2, + }; + expect(@enumToInt(E2.One) == 2); +} + +test "enum literal casting to optional" { + var bar: ?Bar = undefined; + bar = .B; + + 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 + + expect((try bar) == Bar.B); +} + +test "enum with one member and u1 tag type @enumToInt" { + const Enum = enum(u1) { + Test, + }; + expect(@enumToInt(Enum.Test) == 0); +} + +test "enum with comptime_int tag type" { + const Enum = enum(comptime_int) { + One = 3, + Two = 2, + Three = 1, + }; + comptime expect(Tag(Enum) == comptime_int); +} + +test "enum with one member default to u0 tag type" { + const E0 = enum { + X, + }; + comptime expect(Tag(E0) == u0); +} + +test "tagName on enum literals" { + expect(mem.eql(u8, @tagName(.FooBar), "FooBar")); + comptime 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; + expect(e.method()); + expect(e.generic_method(bool)); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/enum_with_members.zig b/test/behavior/enum_with_members.zig new file mode 100644 index 0000000000..08b195494b --- /dev/null +++ b/test/behavior/enum_with_members.zig @@ -0,0 +1,27 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; +const fmt = @import("std").fmt; + +const ET = union(enum) { + SINT: i32, + UINT: u32, + + pub fn print(a: *const ET, buf: []u8) anyerror!usize { + return switch (a.*) { + ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}), + ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}), + }; + } +}; + +test "enum with members" { + const a = ET{ .SINT = -42 }; + const b = ET{ .UINT = 42 }; + var buf: [20]u8 = undefined; + + expect((a.print(buf[0..]) catch unreachable) == 3); + expect(mem.eql(u8, buf[0..3], "-42")); + + expect((b.print(buf[0..]) catch unreachable) == 2); + expect(mem.eql(u8, buf[0..2], "42")); +} diff --git a/test/behavior/error.zig b/test/behavior/error.zig new file mode 100644 index 0000000000..529b89d1e2 --- /dev/null +++ b/test/behavior/error.zig @@ -0,0 +1,452 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectError = std.testing.expectError; +const expectEqual = std.testing.expectEqual; +const mem = std.mem; + +pub fn foo() anyerror!i32 { + const x = try bar(); + return x + 1; +} + +pub fn bar() anyerror!i32 { + return 13; +} + +pub fn baz() anyerror!i32 { + const y = foo() catch 1234; + return y + 1; +} + +test "error wrapping" { + expect((baz() catch unreachable) == 15); +} + +fn gimmeItBroke() []const u8 { + return @errorName(error.ItBroke); +} + +test "@errorName" { + expect(mem.eql(u8, @errorName(error.AnError), "AnError")); + expect(mem.eql(u8, @errorName(error.ALongerErrorName), "ALongerErrorName")); +} + +test "error values" { + const a = @errorToInt(error.err1); + const b = @errorToInt(error.err2); + expect(a != b); +} + +test "redefinition of error values allowed" { + shouldBeNotEqual(error.AnError, error.SecondError); +} +fn shouldBeNotEqual(a: anyerror, b: anyerror) void { + if (a == b) unreachable; +} + +test "error binary operator" { + const a = errBinaryOperatorG(true) catch 3; + const b = errBinaryOperatorG(false) catch 3; + expect(a == 3); + expect(b == 10); +} +fn errBinaryOperatorG(x: bool) anyerror!isize { + return if (x) error.ItBroke else @as(isize, 10); +} + +test "unwrap simple value from error" { + const i = unwrapSimpleValueFromErrorDo() catch unreachable; + expect(i == 13); +} +fn unwrapSimpleValueFromErrorDo() anyerror!isize { + return 13; +} + +test "error return in assignment" { + doErrReturnInAssignment() catch unreachable; +} + +fn doErrReturnInAssignment() anyerror!void { + var x: i32 = undefined; + x = try makeANonErr(); +} + +fn makeANonErr() anyerror!i32 { + return 1; +} + +test "error union type " { + testErrorUnionType(); + comptime testErrorUnionType(); +} + +fn testErrorUnionType() void { + const x: anyerror!i32 = 1234; + if (x) |value| expect(value == 1234) else |_| unreachable; + expect(@typeInfo(@TypeOf(x)) == .ErrorUnion); + expect(@typeInfo(@typeInfo(@TypeOf(x)).ErrorUnion.error_set) == .ErrorSet); + expect(@typeInfo(@TypeOf(x)).ErrorUnion.error_set == anyerror); +} + +test "error set type" { + testErrorSetType(); + comptime testErrorSetType(); +} + +const MyErrSet = error{ + OutOfMemory, + FileNotFound, +}; + +fn testErrorSetType() void { + expect(@typeInfo(MyErrSet).ErrorSet.?.len == 2); + + const a: MyErrSet!i32 = 5678; + const b: MyErrSet!i32 = MyErrSet.OutOfMemory; + + if (a) |value| expect(value == 5678) else |err| switch (err) { + error.OutOfMemory => unreachable, + error.FileNotFound => unreachable, + } +} + +test "explicit error set cast" { + testExplicitErrorSetCast(Set1.A); + comptime testExplicitErrorSetCast(Set1.A); +} + +const Set1 = error{ + A, + B, +}; +const Set2 = error{ + A, + C, +}; + +fn testExplicitErrorSetCast(set1: Set1) void { + var x = @errSetCast(Set2, set1); + var y = @errSetCast(Set1, x); + expect(y == error.A); +} + +test "comptime test error for empty error set" { + testComptimeTestErrorEmptySet(1234); + comptime testComptimeTestErrorEmptySet(1234); +} + +const EmptyErrorSet = error{}; + +fn testComptimeTestErrorEmptySet(x: EmptyErrorSet!i32) void { + if (x) |v| expect(v == 1234) else |err| @compileError("bad"); +} + +test "syntax: optional operator in front of error union operator" { + comptime { + expect(?(anyerror!i32) == ?(anyerror!i32)); + } +} + +test "comptime err to int of error set with only 1 possible value" { + testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); + comptime testErrToIntWithOnePossibleValue(error.A, @errorToInt(error.A)); +} +fn testErrToIntWithOnePossibleValue( + x: error{A}, + comptime value: u32, +) void { + if (@errorToInt(x) != value) { + @compileError("bad"); + } +} + +test "empty error union" { + const x = error{} || error{}; +} + +test "error union peer type resolution" { + testErrorUnionPeerTypeResolution(1); +} + +fn testErrorUnionPeerTypeResolution(x: i32) void { + const y = switch (x) { + 1 => bar_1(), + 2 => baz_1(), + else => quux_1(), + }; + if (y) |_| { + @panic("expected error"); + } else |e| { + expect(e == error.A); + } +} + +fn bar_1() anyerror { + return error.A; +} + +fn baz_1() !i32 { + return error.B; +} + +fn quux_1() !i32 { + return error.C; +} + +test "error: fn returning empty error set can be passed as fn returning any error" { + entry(); + comptime entry(); +} + +fn entry() void { + foo2(bar2); +} + +fn foo2(f: fn () anyerror!void) void { + const x = f(); +} + +fn bar2() (error{}!void) {} + +test "error: Zero sized error set returned with value payload crash" { + _ = foo3(0) catch {}; + _ = comptime foo3(0) catch {}; +} + +const Error = error{}; +fn foo3(b: usize) Error!usize { + return b; +} + +test "error: Infer error set from literals" { + _ = nullLiteral("n") catch |err| handleErrors(err); + _ = floatLiteral("n") catch |err| handleErrors(err); + _ = intLiteral("n") catch |err| handleErrors(err); + _ = comptime nullLiteral("n") catch |err| handleErrors(err); + _ = comptime floatLiteral("n") catch |err| handleErrors(err); + _ = comptime intLiteral("n") catch |err| handleErrors(err); +} + +fn handleErrors(err: anytype) noreturn { + switch (err) { + error.T => {}, + } + + unreachable; +} + +fn nullLiteral(str: []const u8) !?i64 { + if (str[0] == 'n') return null; + + return error.T; +} + +fn floatLiteral(str: []const u8) !?f64 { + if (str[0] == 'n') return 1.0; + + return error.T; +} + +fn intLiteral(str: []const u8) !?i64 { + if (str[0] == 'n') return 1; + + return error.T; +} + +test "nested error union function call in optional unwrap" { + const S = struct { + const Foo = struct { + a: i32, + }; + + fn errorable() !i32 { + var x: Foo = (try getFoo()) orelse return error.Other; + return x.a; + } + + fn errorable2() !i32 { + var x: Foo = (try getFoo2()) orelse return error.Other; + return x.a; + } + + fn errorable3() !i32 { + var x: Foo = (try getFoo3()) orelse return error.Other; + return x.a; + } + + fn getFoo() anyerror!?Foo { + return Foo{ .a = 1234 }; + } + + fn getFoo2() anyerror!?Foo { + return error.Failure; + } + + fn getFoo3() anyerror!?Foo { + return null; + } + }; + expect((try S.errorable()) == 1234); + expectError(error.Failure, S.errorable2()); + expectError(error.Other, S.errorable3()); + comptime { + expect((try S.errorable()) == 1234); + expectError(error.Failure, S.errorable2()); + expectError(error.Other, S.errorable3()); + } +} + +test "widen cast integer payload of error union function call" { + const S = struct { + fn errorable() !u64 { + var x = @as(u64, try number()); + return x; + } + + fn number() anyerror!u32 { + return 1234; + } + }; + expect((try S.errorable()) == 1234); +} + +test "return function call to error set from error union function" { + const S = struct { + fn errorable() anyerror!i32 { + return fail(); + } + + fn fail() anyerror { + return error.Failure; + } + }; + expectError(error.Failure, S.errorable()); + comptime expectError(error.Failure, S.errorable()); +} + +test "optional error set is the same size as error set" { + comptime expect(@sizeOf(?anyerror) == @sizeOf(anyerror)); + const S = struct { + fn returnsOptErrSet() ?anyerror { + return null; + } + }; + expect(S.returnsOptErrSet() == null); + comptime expect(S.returnsOptErrSet() == null); +} + +test "debug info for optional error set" { + const SomeError = error{Hello}; + var a_local_variable: ?SomeError = null; +} + +test "nested catch" { + const S = struct { + fn entry() void { + expectError(error.Bad, func()); + } + fn fail() anyerror!Foo { + return error.Wrong; + } + fn func() anyerror!Foo { + const x = fail() catch + fail() catch + return error.Bad; + unreachable; + } + const Foo = struct { + field: i32, + }; + }; + S.entry(); + comptime S.entry(); +} + +test "implicit cast to optional to error union to return result loc" { + const S = struct { + fn entry() void { + var x: Foo = undefined; + if (func(&x)) |opt| { + expect(opt != null); + } else |_| @panic("expected non error"); + } + fn func(f: *Foo) anyerror!?*Foo { + return f; + } + const Foo = struct { + field: i32, + }; + }; + S.entry(); + //comptime S.entry(); TODO +} + +test "function pointer with return type that is error union with payload which is pointer of parent struct" { + const S = struct { + const Foo = struct { + fun: fn (a: i32) (anyerror!*Foo), + }; + + const Err = error{UnspecifiedErr}; + + fn bar(a: i32) anyerror!*Foo { + return Err.UnspecifiedErr; + } + + fn doTheTest() void { + var x = Foo{ .fun = bar }; + expectError(error.UnspecifiedErr, x.fun(1)); + } + }; + S.doTheTest(); +} + +test "return result loc as peer result loc in inferred error set function" { + const S = struct { + fn doTheTest() void { + if (foo(2)) |x| { + expect(x.Two); + } else |e| switch (e) { + error.Whatever => @panic("fail"), + } + expectError(error.Whatever, foo(99)); + } + const FormValue = union(enum) { + One: void, + Two: bool, + }; + + fn foo(id: u64) !FormValue { + return switch (id) { + 2 => FormValue{ .Two = true }, + 1 => FormValue{ .One = {} }, + else => return error.Whatever, + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "error payload type is correctly resolved" { + const MyIntWrapper = struct { + const Self = @This(); + + x: i32, + + pub fn create() anyerror!Self { + return Self{ .x = 42 }; + } + }; + + expectEqual(MyIntWrapper{ .x = 42 }, try MyIntWrapper.create()); +} + +test "error union comptime caching" { + const S = struct { + fn foo(comptime arg: anytype) void {} + }; + + S.foo(@as(anyerror!void, {})); + S.foo(@as(anyerror!void, {})); +}
\ No newline at end of file diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig new file mode 100644 index 0000000000..05e0a7742d --- /dev/null +++ b/test/behavior/eval.zig @@ -0,0 +1,832 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +test "compile time recursion" { + expect(some_data.len == 21); +} +var some_data: [@intCast(usize, fibonacci(7))]u8 = undefined; +fn fibonacci(x: i32) i32 { + if (x <= 1) return 1; + return fibonacci(x - 1) + fibonacci(x - 2); +} + +fn unwrapAndAddOne(blah: ?i32) i32 { + return blah.? + 1; +} +const should_be_1235 = unwrapAndAddOne(1234); +test "static add one" { + expect(should_be_1235 == 1235); +} + +test "inlined loop" { + comptime var i = 0; + comptime var sum = 0; + inline while (i <= 5) : (i += 1) + sum += i; + expect(sum == 15); +} + +fn gimme1or2(comptime a: bool) i32 { + const x: i32 = 1; + const y: i32 = 2; + comptime var z: i32 = if (a) x else y; + return z; +} +test "inline variable gets result of const if" { + expect(gimme1or2(true) == 1); + expect(gimme1or2(false) == 2); +} + +test "static function evaluation" { + expect(statically_added_number == 3); +} +const statically_added_number = staticAdd(1, 2); +fn staticAdd(a: i32, b: i32) i32 { + return a + b; +} + +test "const expr eval on single expr blocks" { + expect(constExprEvalOnSingleExprBlocksFn(1, true) == 3); + comptime expect(constExprEvalOnSingleExprBlocksFn(1, true) == 3); +} + +fn constExprEvalOnSingleExprBlocksFn(x: i32, b: bool) i32 { + const literal = 3; + + const result = if (b) b: { + break :b literal; + } else b: { + break :b x; + }; + + return result; +} + +test "statically initialized list" { + expect(static_point_list[0].x == 1); + expect(static_point_list[0].y == 2); + expect(static_point_list[1].x == 3); + expect(static_point_list[1].y == 4); +} +const Point = struct { + x: i32, + y: i32, +}; +const static_point_list = [_]Point{ + makePoint(1, 2), + makePoint(3, 4), +}; +fn makePoint(x: i32, y: i32) Point { + return Point{ + .x = x, + .y = y, + }; +} + +test "static eval list init" { + expect(static_vec3.data[2] == 1.0); + expect(vec3(0.0, 0.0, 3.0).data[2] == 3.0); +} +const static_vec3 = vec3(0.0, 0.0, 1.0); +pub const Vec3 = struct { + data: [3]f32, +}; +pub fn vec3(x: f32, y: f32, z: f32) Vec3 { + return Vec3{ + .data = [_]f32{ + x, + y, + z, + }, + }; +} + +test "constant expressions" { + var array: [array_size]u8 = undefined; + expect(@sizeOf(@TypeOf(array)) == 20); +} +const array_size: u8 = 20; + +test "constant struct with negation" { + expect(vertices[0].x == -0.6); +} +const Vertex = struct { + x: f32, + y: f32, + r: f32, + g: f32, + b: f32, +}; +const vertices = [_]Vertex{ + Vertex{ + .x = -0.6, + .y = -0.4, + .r = 1.0, + .g = 0.0, + .b = 0.0, + }, + Vertex{ + .x = 0.6, + .y = -0.4, + .r = 0.0, + .g = 1.0, + .b = 0.0, + }, + Vertex{ + .x = 0.0, + .y = 0.6, + .r = 0.0, + .g = 0.0, + .b = 1.0, + }, +}; + +test "statically initialized struct" { + st_init_str_foo.x += 1; + expect(st_init_str_foo.x == 14); +} +const StInitStrFoo = struct { + x: i32, + y: bool, +}; +var st_init_str_foo = StInitStrFoo{ + .x = 13, + .y = true, +}; + +test "statically initalized array literal" { + const y: [4]u8 = st_init_arr_lit_x; + expect(y[3] == 4); +} +const st_init_arr_lit_x = [_]u8{ + 1, + 2, + 3, + 4, +}; + +test "const slice" { + comptime { + const a = "1234567890"; + expect(a.len == 10); + const b = a[1..2]; + expect(b.len == 1); + expect(b[0] == '2'); + } +} + +test "try to trick eval with runtime if" { + expect(testTryToTrickEvalWithRuntimeIf(true) == 10); +} + +fn testTryToTrickEvalWithRuntimeIf(b: bool) usize { + comptime var i: usize = 0; + inline while (i < 10) : (i += 1) { + const result = if (b) false else true; + } + comptime { + return i; + } +} + +test "inlined loop has array literal with elided runtime scope on first iteration but not second iteration" { + var runtime = [1]i32{3}; + comptime var i: usize = 0; + inline while (i < 2) : (i += 1) { + const result = if (i == 0) [1]i32{2} else runtime; + } + comptime { + expect(i == 2); + } +} + +fn max(comptime T: type, a: T, b: T) T { + if (T == bool) { + return a or b; + } else if (a > b) { + return a; + } else { + return b; + } +} +fn letsTryToCompareBools(a: bool, b: bool) bool { + return max(bool, a, b); +} +test "inlined block and runtime block phi" { + expect(letsTryToCompareBools(true, true)); + expect(letsTryToCompareBools(true, false)); + expect(letsTryToCompareBools(false, true)); + expect(!letsTryToCompareBools(false, false)); + + comptime { + expect(letsTryToCompareBools(true, true)); + expect(letsTryToCompareBools(true, false)); + expect(letsTryToCompareBools(false, true)); + expect(!letsTryToCompareBools(false, false)); + } +} + +const CmdFn = struct { + name: []const u8, + func: fn (i32) i32, +}; + +const cmd_fns = [_]CmdFn{ + CmdFn{ + .name = "one", + .func = one, + }, + CmdFn{ + .name = "two", + .func = two, + }, + CmdFn{ + .name = "three", + .func = three, + }, +}; +fn one(value: i32) i32 { + return value + 1; +} +fn two(value: i32) i32 { + return value + 2; +} +fn three(value: i32) i32 { + return value + 3; +} + +fn performFn(comptime prefix_char: u8, start_value: i32) i32 { + var result: i32 = start_value; + comptime var i = 0; + inline while (i < cmd_fns.len) : (i += 1) { + if (cmd_fns[i].name[0] == prefix_char) { + result = cmd_fns[i].func(result); + } + } + return result; +} + +test "comptime iterate over fn ptr list" { + expect(performFn('t', 1) == 6); + expect(performFn('o', 0) == 1); + expect(performFn('w', 99) == 99); +} + +test "eval @setRuntimeSafety at compile-time" { + const result = comptime fnWithSetRuntimeSafety(); + expect(result == 1234); +} + +fn fnWithSetRuntimeSafety() i32 { + @setRuntimeSafety(true); + return 1234; +} + +test "eval @setFloatMode at compile-time" { + const result = comptime fnWithFloatMode(); + expect(result == 1234.0); +} + +fn fnWithFloatMode() f32 { + @setFloatMode(std.builtin.FloatMode.Strict); + return 1234.0; +} + +const SimpleStruct = struct { + field: i32, + + fn method(self: *const SimpleStruct) i32 { + return self.field + 3; + } +}; + +var simple_struct = SimpleStruct{ .field = 1234 }; + +const bound_fn = simple_struct.method; + +test "call method on bound fn referring to var instance" { + expect(bound_fn() == 1237); +} + +test "ptr to local array argument at comptime" { + comptime { + var bytes: [10]u8 = undefined; + modifySomeBytes(bytes[0..]); + expect(bytes[0] == 'a'); + expect(bytes[9] == 'b'); + } +} + +fn modifySomeBytes(bytes: []u8) void { + bytes[0] = 'a'; + bytes[9] = 'b'; +} + +test "comparisons 0 <= uint and 0 > uint should be comptime" { + testCompTimeUIntComparisons(1234); +} +fn testCompTimeUIntComparisons(x: u32) void { + if (!(0 <= x)) { + @compileError("this condition should be comptime known"); + } + if (0 > x) { + @compileError("this condition should be comptime known"); + } + if (!(x >= 0)) { + @compileError("this condition should be comptime known"); + } + if (x < 0) { + @compileError("this condition should be comptime known"); + } +} + +test "const ptr to variable data changes at runtime" { + expect(foo_ref.name[0] == 'a'); + foo_ref.name = "b"; + expect(foo_ref.name[0] == 'b'); +} + +const Foo = struct { + name: []const u8, +}; + +var foo_contents = Foo{ .name = "a" }; +const foo_ref = &foo_contents; + +test "create global array with for loop" { + expect(global_array[5] == 5 * 5); + expect(global_array[9] == 9 * 9); +} + +const global_array = x: { + var result: [10]usize = undefined; + for (result) |*item, index| { + item.* = index * index; + } + break :x result; +}; + +test "compile-time downcast when the bits fit" { + comptime { + const spartan_count: u16 = 255; + const byte = @intCast(u8, spartan_count); + expect(byte == 255); + } +} + +const hi1 = "hi"; +const hi2 = hi1; +test "const global shares pointer with other same one" { + assertEqualPtrs(&hi1[0], &hi2[0]); + comptime expect(&hi1[0] == &hi2[0]); +} +fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) void { + expect(ptr1 == ptr2); +} + +test "@setEvalBranchQuota" { + comptime { + // 1001 for the loop and then 1 more for the expect fn call + @setEvalBranchQuota(1002); + var i = 0; + var sum = 0; + while (i < 1001) : (i += 1) { + sum += i; + } + expect(sum == 500500); + } +} + +test "float literal at compile time not lossy" { + expect(16777216.0 + 1.0 == 16777217.0); + expect(9007199254740992.0 + 1.0 == 9007199254740993.0); +} + +test "f32 at compile time is lossy" { + expect(@as(f32, 1 << 24) + 1 == 1 << 24); +} + +test "f64 at compile time is lossy" { + expect(@as(f64, 1 << 53) + 1 == 1 << 53); +} + +test "f128 at compile time is lossy" { + expect(@as(f128, 10384593717069655257060992658440192.0) + 1 == 10384593717069655257060992658440192.0); +} + +comptime { + expect(@as(f128, 1 << 113) == 10384593717069655257060992658440192); +} + +pub fn TypeWithCompTimeSlice(comptime field_name: []const u8) type { + return struct { + pub const Node = struct {}; + }; +} + +test "string literal used as comptime slice is memoized" { + const a = "link"; + const b = "link"; + comptime expect(TypeWithCompTimeSlice(a).Node == TypeWithCompTimeSlice(b).Node); + comptime expect(TypeWithCompTimeSlice("link").Node == TypeWithCompTimeSlice("link").Node); +} + +test "comptime slice of undefined pointer of length 0" { + const slice1 = @as([*]i32, undefined)[0..0]; + expect(slice1.len == 0); + const slice2 = @as([*]i32, undefined)[100..100]; + expect(slice2.len == 0); +} + +fn copyWithPartialInline(s: []u32, b: []u8) void { + comptime var i: usize = 0; + inline while (i < 4) : (i += 1) { + s[i] = 0; + s[i] |= @as(u32, b[i * 4 + 0]) << 24; + s[i] |= @as(u32, b[i * 4 + 1]) << 16; + s[i] |= @as(u32, b[i * 4 + 2]) << 8; + s[i] |= @as(u32, b[i * 4 + 3]) << 0; + } +} + +test "binary math operator in partially inlined function" { + var s: [4]u32 = undefined; + var b: [16]u8 = undefined; + + for (b) |*r, i| + r.* = @intCast(u8, i + 1); + + copyWithPartialInline(s[0..], b[0..]); + expect(s[0] == 0x1020304); + expect(s[1] == 0x5060708); + expect(s[2] == 0x90a0b0c); + expect(s[3] == 0xd0e0f10); +} + +test "comptime function with the same args is memoized" { + comptime { + expect(MakeType(i32) == MakeType(i32)); + expect(MakeType(i32) != MakeType(f64)); + } +} + +fn MakeType(comptime T: type) type { + return struct { + field: T, + }; +} + +test "comptime function with mutable pointer is not memoized" { + comptime { + var x: i32 = 1; + const ptr = &x; + increment(ptr); + increment(ptr); + expect(x == 3); + } +} + +fn increment(value: *i32) void { + value.* += 1; +} + +fn generateTable(comptime T: type) [1010]T { + var res: [1010]T = undefined; + var i: usize = 0; + while (i < 1010) : (i += 1) { + res[i] = @intCast(T, i); + } + return res; +} + +fn doesAlotT(comptime T: type, value: usize) T { + @setEvalBranchQuota(5000); + const table = comptime blk: { + break :blk generateTable(T); + }; + return table[value]; +} + +test "@setEvalBranchQuota at same scope as generic function call" { + expect(doesAlotT(u32, 2) == 2); +} + +test "comptime slice of slice preserves comptime var" { + comptime { + var buff: [10]u8 = undefined; + buff[0..][0..][0] = 1; + expect(buff[0..][0..][0] == 1); + } +} + +test "comptime slice of pointer preserves comptime var" { + comptime { + var buff: [10]u8 = undefined; + var a = @ptrCast([*]u8, &buff); + a[0..1][0] = 1; + expect(buff[0..][0..][0] == 1); + } +} + +const SingleFieldStruct = struct { + x: i32, + + fn read_x(self: *const SingleFieldStruct) i32 { + return self.x; + } +}; +test "const ptr to comptime mutable data is not memoized" { + comptime { + var foo = SingleFieldStruct{ .x = 1 }; + expect(foo.read_x() == 1); + foo.x = 2; + expect(foo.read_x() == 2); + } +} + +test "array concat of slices gives slice" { + comptime { + var a: []const u8 = "aoeu"; + var b: []const u8 = "asdf"; + const c = a ++ b; + expect(std.mem.eql(u8, c, "aoeuasdf")); + } +} + +test "comptime shlWithOverflow" { + const ct_shifted: u64 = comptime amt: { + var amt = @as(u64, 0); + _ = @shlWithOverflow(u64, ~@as(u64, 0), 16, &amt); + break :amt amt; + }; + + const rt_shifted: u64 = amt: { + var amt = @as(u64, 0); + _ = @shlWithOverflow(u64, ~@as(u64, 0), 16, &amt); + break :amt amt; + }; + + expect(ct_shifted == rt_shifted); +} + +test "runtime 128 bit integer division" { + var a: u128 = 152313999999999991610955792383; + var b: u128 = 10000000000000000000; + var c = a / b; + expect(c == 15231399999); +} + +pub const Info = struct { + version: u8, +}; + +pub const diamond_info = Info{ .version = 0 }; + +test "comptime modification of const struct field" { + comptime { + var res = diamond_info; + res.version = 1; + expect(diamond_info.version == 0); + expect(res.version == 1); + } +} + +test "pointer to type" { + comptime { + var T: type = i32; + expect(T == i32); + var ptr = &T; + expect(@TypeOf(ptr) == *type); + ptr.* = f32; + expect(T == f32); + expect(*T == *f32); + } +} + +test "slice of type" { + comptime { + var types_array = [_]type{ i32, f64, type }; + for (types_array) |T, i| { + switch (i) { + 0 => expect(T == i32), + 1 => expect(T == f64), + 2 => expect(T == type), + else => unreachable, + } + } + for (types_array[0..]) |T, i| { + switch (i) { + 0 => expect(T == i32), + 1 => expect(T == f64), + 2 => expect(T == type), + else => unreachable, + } + } + } +} + +const Wrapper = struct { + T: type, +}; + +fn wrap(comptime T: type) Wrapper { + return Wrapper{ .T = T }; +} + +test "function which returns struct with type field causes implicit comptime" { + const ty = wrap(i32).T; + expect(ty == i32); +} + +test "call method with comptime pass-by-non-copying-value self parameter" { + const S = struct { + a: u8, + + fn b(comptime s: @This()) u8 { + return s.a; + } + }; + + const s = S{ .a = 2 }; + var b = s.b(); + expect(b == 2); +} + +test "@tagName of @typeInfo" { + const str = @tagName(@typeInfo(u8)); + expect(std.mem.eql(u8, str, "Int")); +} + +test "setting backward branch quota just before a generic fn call" { + @setEvalBranchQuota(1001); + loopNTimes(1001); +} + +fn loopNTimes(comptime n: usize) void { + comptime var i = 0; + inline while (i < n) : (i += 1) {} +} + +test "variable inside inline loop that has different types on different iterations" { + testVarInsideInlineLoop(.{ true, @as(u32, 42) }); +} + +fn testVarInsideInlineLoop(args: anytype) void { + comptime var i = 0; + inline while (i < args.len) : (i += 1) { + const x = args[i]; + if (i == 0) expect(x); + if (i == 1) expect(x == 42); + } +} + +test "inline for with same type but different values" { + var res: usize = 0; + inline for ([_]type{ [2]u8, [1]u8, [2]u8 }) |T| { + var a: T = undefined; + res += a.len; + } + expect(res == 5); +} + +test "refer to the type of a generic function" { + const Func = fn (type) void; + const f: Func = doNothingWithType; + f(i32); +} + +fn doNothingWithType(comptime T: type) void {} + +test "zero extend from u0 to u1" { + var zero_u0: u0 = 0; + var zero_u1: u1 = zero_u0; + expect(zero_u1 == 0); +} + +test "bit shift a u1" { + var x: u1 = 1; + var y = x << 0; + expect(y == 1); +} + +test "comptime pointer cast array and then slice" { + const array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; + + const ptrA: [*]const u8 = @ptrCast([*]const u8, &array); + const sliceA: []const u8 = ptrA[0..2]; + + const ptrB: [*]const u8 = &array; + const sliceB: []const u8 = ptrB[0..2]; + + expect(sliceA[1] == 2); + expect(sliceB[1] == 2); +} + +test "slice bounds in comptime concatenation" { + const bs = comptime blk: { + const b = "........1........"; + break :blk b[8..9]; + }; + const str = "" ++ bs; + expect(str.len == 1); + expect(std.mem.eql(u8, str, "1")); + + const str2 = bs ++ ""; + expect(str2.len == 1); + expect(std.mem.eql(u8, str2, "1")); +} + +test "comptime bitwise operators" { + comptime { + expect(3 & 1 == 1); + expect(3 & -1 == 3); + expect(-3 & -1 == -3); + expect(3 | -1 == -1); + expect(-3 | -1 == -1); + expect(3 ^ -1 == -4); + expect(-3 ^ -1 == 2); + expect(~@as(i8, -1) == 0); + expect(~@as(i128, -1) == 0); + expect(18446744073709551615 & 18446744073709551611 == 18446744073709551611); + expect(-18446744073709551615 & -18446744073709551611 == -18446744073709551615); + expect(~@as(u128, 0) == 0xffffffffffffffffffffffffffffffff); + } +} + +test "*align(1) u16 is the same as *align(1:0:2) u16" { + comptime { + expect(*align(1:0:2) u16 == *align(1) u16); + expect(*align(2:0:2) u16 == *u16); + } +} + +test "array concatenation forces comptime" { + var a = oneItem(3) ++ oneItem(4); + expect(std.mem.eql(i32, &a, &[_]i32{ 3, 4 })); +} + +test "array multiplication forces comptime" { + var a = oneItem(3) ** scalar(2); + expect(std.mem.eql(i32, &a, &[_]i32{ 3, 3 })); +} + +fn oneItem(x: i32) [1]i32 { + return [_]i32{x}; +} + +fn scalar(x: u32) u32 { + return x; +} + +test "no undeclared identifier error in unanalyzed branches" { + if (false) { + lol_this_doesnt_exist = nonsense; + } +} + +test "comptime assign int to optional int" { + comptime { + var x: ?i32 = null; + x = 2; + x.? *= 10; + expectEqual(20, x.?); + } +} + +test "return 0 from function that has u0 return type" { + const S = struct { + fn foo_zero() u0 { + return 0; + } + }; + comptime { + if (S.foo_zero() != 0) { + @compileError("test failed"); + } + } +} + +test "two comptime calls with array default initialized to undefined" { + const S = struct { + const CrossTarget = struct { + dynamic_linker: DynamicLinker = DynamicLinker{}, + + pub fn parse() void { + var result: CrossTarget = .{}; + result.getCpuArch(); + } + + pub fn getCpuArch(self: CrossTarget) void {} + }; + + const DynamicLinker = struct { + buffer: [255]u8 = undefined, + }; + }; + + comptime { + S.CrossTarget.parse(); + S.CrossTarget.parse(); + } +} diff --git a/test/behavior/field_parent_ptr.zig b/test/behavior/field_parent_ptr.zig new file mode 100644 index 0000000000..6026a49d12 --- /dev/null +++ b/test/behavior/field_parent_ptr.zig @@ -0,0 +1,41 @@ +const expect = @import("std").testing.expect; + +test "@fieldParentPtr non-first field" { + testParentFieldPtr(&foo.c); + comptime testParentFieldPtr(&foo.c); +} + +test "@fieldParentPtr first field" { + testParentFieldPtrFirst(&foo.a); + comptime testParentFieldPtrFirst(&foo.a); +} + +const Foo = struct { + a: bool, + b: f32, + c: i32, + d: i32, +}; + +const foo = Foo{ + .a = true, + .b = 0.123, + .c = 1234, + .d = -10, +}; + +fn testParentFieldPtr(c: *const i32) void { + expect(c == &foo.c); + + const base = @fieldParentPtr(Foo, "c", c); + expect(base == &foo); + expect(&base.c == c); +} + +fn testParentFieldPtrFirst(a: *const bool) void { + expect(a == &foo.a); + + const base = @fieldParentPtr(Foo, "a", a); + expect(base == &foo); + expect(&base.a == a); +} diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig new file mode 100644 index 0000000000..9f2e45ed26 --- /dev/null +++ b/test/behavior/floatop.zig @@ -0,0 +1,465 @@ +const std = @import("std"); +const expect = std.testing.expect; +const math = std.math; +const pi = std.math.pi; +const e = std.math.e; +const Vector = std.meta.Vector; + +const epsilon = 0.000001; + +test "@sqrt" { + comptime testSqrt(); + testSqrt(); +} + +fn testSqrt() void { + { + var a: f16 = 4; + expect(@sqrt(a) == 2); + } + { + var a: f32 = 9; + expect(@sqrt(a) == 3); + var b: f32 = 1.1; + expect(math.approxEqAbs(f32, @sqrt(b), 1.0488088481701516, epsilon)); + } + { + var a: f64 = 25; + expect(@sqrt(a) == 5); + } + { + const a: comptime_float = 25.0; + expect(@sqrt(a) == 5.0); + } + // TODO https://github.com/ziglang/zig/issues/4026 + //{ + // var a: f128 = 49; + // expect(@sqrt(a) == 7); + //} + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 3.3, 4.4 }; + var result = @sqrt(v); + expect(math.approxEqAbs(f32, @sqrt(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @sqrt(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @sqrt(@as(f32, 3.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @sqrt(@as(f32, 4.4)), result[3], epsilon)); + } +} + +test "more @sqrt f16 tests" { + // TODO these are not all passing at comptime + expect(@sqrt(@as(f16, 0.0)) == 0.0); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 2.0)), 1.414214, epsilon)); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 3.6)), 1.897367, epsilon)); + expect(@sqrt(@as(f16, 4.0)) == 2.0); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 7.539840)), 2.745877, epsilon)); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 19.230934)), 4.385309, epsilon)); + expect(@sqrt(@as(f16, 64.0)) == 8.0); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 64.1)), 8.006248, epsilon)); + expect(math.approxEqAbs(f16, @sqrt(@as(f16, 8942.230469)), 94.563370, epsilon)); + + // special cases + expect(math.isPositiveInf(@sqrt(@as(f16, math.inf(f16))))); + expect(@sqrt(@as(f16, 0.0)) == 0.0); + expect(@sqrt(@as(f16, -0.0)) == -0.0); + expect(math.isNan(@sqrt(@as(f16, -1.0)))); + expect(math.isNan(@sqrt(@as(f16, math.nan(f16))))); +} + +test "@sin" { + comptime testSin(); + testSin(); +} + +fn testSin() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 0; + expect(@sin(a) == 0); + } + { + var a: f32 = 0; + expect(@sin(a) == 0); + } + { + var a: f64 = 0; + expect(@sin(a) == 0); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 3.3, 4.4 }; + var result = @sin(v); + expect(math.approxEqAbs(f32, @sin(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @sin(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @sin(@as(f32, 3.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @sin(@as(f32, 4.4)), result[3], epsilon)); + } +} + +test "@cos" { + comptime testCos(); + testCos(); +} + +fn testCos() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 0; + expect(@cos(a) == 1); + } + { + var a: f32 = 0; + expect(@cos(a) == 1); + } + { + var a: f64 = 0; + expect(@cos(a) == 1); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 3.3, 4.4 }; + var result = @cos(v); + expect(math.approxEqAbs(f32, @cos(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @cos(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @cos(@as(f32, 3.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @cos(@as(f32, 4.4)), result[3], epsilon)); + } +} + +test "@exp" { + comptime testExp(); + testExp(); +} + +fn testExp() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 0; + expect(@exp(a) == 1); + } + { + var a: f32 = 0; + expect(@exp(a) == 1); + } + { + var a: f64 = 0; + expect(@exp(a) == 1); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 }; + var result = @exp(v); + expect(math.approxEqAbs(f32, @exp(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @exp(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @exp(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @exp(@as(f32, 0.4)), result[3], epsilon)); + } +} + +test "@exp2" { + comptime testExp2(); + testExp2(); +} + +fn testExp2() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 2; + expect(@exp2(a) == 4); + } + { + var a: f32 = 2; + expect(@exp2(a) == 4); + } + { + var a: f64 = 2; + expect(@exp2(a) == 4); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 }; + var result = @exp2(v); + expect(math.approxEqAbs(f32, @exp2(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @exp2(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @exp2(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @exp2(@as(f32, 0.4)), result[3], epsilon)); + } +} + +test "@log" { + // Old musl (and glibc?), and our current math.ln implementation do not return 1 + // so also accept those values. + comptime testLog(); + testLog(); +} + +fn testLog() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = e; + expect(math.approxEqAbs(f16, @log(a), 1, epsilon)); + } + { + var a: f32 = e; + expect(@log(a) == 1 or @log(a) == @bitCast(f32, @as(u32, 0x3f7fffff))); + } + { + var a: f64 = e; + expect(@log(a) == 1 or @log(a) == @bitCast(f64, @as(u64, 0x3ff0000000000000))); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 }; + var result = @log(v); + expect(math.approxEqAbs(f32, @log(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @log(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @log(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @log(@as(f32, 0.4)), result[3], epsilon)); + } +} + +test "@log2" { + comptime testLog2(); + testLog2(); +} + +fn testLog2() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 4; + expect(@log2(a) == 2); + } + { + var a: f32 = 4; + expect(@log2(a) == 2); + } + { + var a: f64 = 4; + expect(@log2(a) == 2); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 }; + var result = @log2(v); + expect(math.approxEqAbs(f32, @log2(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @log2(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @log2(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @log2(@as(f32, 0.4)), result[3], epsilon)); + } +} + +test "@log10" { + comptime testLog10(); + testLog10(); +} + +fn testLog10() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 100; + expect(@log10(a) == 2); + } + { + var a: f32 = 100; + expect(@log10(a) == 2); + } + { + var a: f64 = 1000; + expect(@log10(a) == 3); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 }; + var result = @log10(v); + expect(math.approxEqAbs(f32, @log10(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @log10(@as(f32, 2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @log10(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @log10(@as(f32, 0.4)), result[3], epsilon)); + } +} + +test "@fabs" { + comptime testFabs(); + testFabs(); +} + +fn testFabs() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = -2.5; + var b: f16 = 2.5; + expect(@fabs(a) == 2.5); + expect(@fabs(b) == 2.5); + } + { + var a: f32 = -2.5; + var b: f32 = 2.5; + expect(@fabs(a) == 2.5); + expect(@fabs(b) == 2.5); + } + { + var a: f64 = -2.5; + var b: f64 = 2.5; + expect(@fabs(a) == 2.5); + expect(@fabs(b) == 2.5); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, -2.2, 0.3, -0.4 }; + var result = @fabs(v); + expect(math.approxEqAbs(f32, @fabs(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @fabs(@as(f32, -2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @fabs(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @fabs(@as(f32, -0.4)), result[3], epsilon)); + } +} + +test "@floor" { + comptime testFloor(); + testFloor(); +} + +fn testFloor() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 2.1; + expect(@floor(a) == 2); + } + { + var a: f32 = 2.1; + expect(@floor(a) == 2); + } + { + var a: f64 = 3.5; + expect(@floor(a) == 3); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, -2.2, 0.3, -0.4 }; + var result = @floor(v); + expect(math.approxEqAbs(f32, @floor(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @floor(@as(f32, -2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @floor(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @floor(@as(f32, -0.4)), result[3], epsilon)); + } +} + +test "@ceil" { + comptime testCeil(); + testCeil(); +} + +fn testCeil() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 2.1; + expect(@ceil(a) == 3); + } + { + var a: f32 = 2.1; + expect(@ceil(a) == 3); + } + { + var a: f64 = 3.5; + expect(@ceil(a) == 4); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, -2.2, 0.3, -0.4 }; + var result = @ceil(v); + expect(math.approxEqAbs(f32, @ceil(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @ceil(@as(f32, -2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @ceil(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @ceil(@as(f32, -0.4)), result[3], epsilon)); + } +} + +test "@trunc" { + comptime testTrunc(); + testTrunc(); +} + +fn testTrunc() void { + // TODO test f128, and c_longdouble + // https://github.com/ziglang/zig/issues/4026 + { + var a: f16 = 2.1; + expect(@trunc(a) == 2); + } + { + var a: f32 = 2.1; + expect(@trunc(a) == 2); + } + { + var a: f64 = -3.5; + expect(@trunc(a) == -3); + } + { + var v: Vector(4, f32) = [_]f32{ 1.1, -2.2, 0.3, -0.4 }; + var result = @trunc(v); + expect(math.approxEqAbs(f32, @trunc(@as(f32, 1.1)), result[0], epsilon)); + expect(math.approxEqAbs(f32, @trunc(@as(f32, -2.2)), result[1], epsilon)); + expect(math.approxEqAbs(f32, @trunc(@as(f32, 0.3)), result[2], epsilon)); + expect(math.approxEqAbs(f32, @trunc(@as(f32, -0.4)), result[3], epsilon)); + } +} + +test "floating point comparisons" { + testFloatComparisons(); + comptime testFloatComparisons(); +} + +fn testFloatComparisons() void { + inline for ([_]type{ f16, f32, f64, f128 }) |ty| { + // No decimal part + { + const x: ty = 1.0; + expect(x == 1); + expect(x != 0); + expect(x > 0); + expect(x < 2); + expect(x >= 1); + expect(x <= 1); + } + // Non-zero decimal part + { + const x: ty = 1.5; + expect(x != 1); + expect(x != 2); + expect(x > 1); + expect(x < 2); + expect(x >= 1); + expect(x <= 2); + } + } +} + +test "different sized float comparisons" { + testDifferentSizedFloatComparisons(); + comptime testDifferentSizedFloatComparisons(); +} + +fn testDifferentSizedFloatComparisons() void { + var a: f16 = 1; + var b: f64 = 2; + expect(a < b); +} + +// TODO This is waiting on library support for the Windows build (not sure why the other's don't need it) +//test "@nearbyint" { +// comptime testNearbyInt(); +// testNearbyInt(); +//} + +//fn testNearbyInt() void { +// // TODO test f16, f128, and c_longdouble +// // https://github.com/ziglang/zig/issues/4026 +// { +// var a: f32 = 2.1; +// expect(@nearbyint(a) == 2); +// } +// { +// var a: f64 = -3.75; +// expect(@nearbyint(a) == -4); +// } +//} diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig new file mode 100644 index 0000000000..6cefee6a01 --- /dev/null +++ b/test/behavior/fn.zig @@ -0,0 +1,287 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "params" { + expect(testParamsAdd(22, 11) == 33); +} +fn testParamsAdd(a: i32, b: i32) i32 { + return a + b; +} + +test "local variables" { + testLocVars(2); +} +fn testLocVars(b: i32) void { + const a: i32 = 1; + if (a + b != 3) unreachable; +} + +test "void parameters" { + voidFun(1, void{}, 2, {}); +} +fn voidFun(a: i32, b: void, c: i32, d: void) void { + const v = b; + const vv: void = if (a == 1) v else {}; + expect(a + c == 3); + return vv; +} + +test "mutable local variables" { + var zero: i32 = 0; + expect(zero == 0); + + var i = @as(i32, 0); + while (i != 3) { + i += 1; + } + expect(i == 3); +} + +test "separate block scopes" { + { + const no_conflict: i32 = 5; + expect(no_conflict == 5); + } + + const c = x: { + const no_conflict = @as(i32, 10); + break :x no_conflict; + }; + expect(c == 10); +} + +test "call function with empty string" { + acceptsString(""); +} + +fn acceptsString(foo: []u8) void {} + +fn @"weird function name"() i32 { + return 1234; +} +test "weird function name" { + expect(@"weird function name"() == 1234); +} + +test "implicit cast function unreachable return" { + wantsFnWithVoid(fnWithUnreachable); +} + +fn wantsFnWithVoid(f: fn () void) void {} + +fn fnWithUnreachable() noreturn { + unreachable; +} + +test "function pointers" { + const fns = [_]@TypeOf(fn1){ + fn1, + fn2, + fn3, + fn4, + }; + for (fns) |f, i| { + expect(f() == @intCast(u32, i) + 5); + } +} +fn fn1() u32 { + return 5; +} +fn fn2() u32 { + return 6; +} +fn fn3() u32 { + return 7; +} +fn fn4() u32 { + return 8; +} + +test "number literal as an argument" { + numberLiteralArg(3); + comptime numberLiteralArg(3); +} + +fn numberLiteralArg(a: anytype) void { + expect(a == 3); +} + +test "assign inline fn to const variable" { + const a = inlineFn; + a(); +} + +fn inlineFn() callconv(.Inline) void {} + +test "pass by non-copying value" { + expect(addPointCoords(Point{ .x = 1, .y = 2 }) == 3); +} + +const Point = struct { + x: i32, + y: i32, +}; + +fn addPointCoords(pt: Point) i32 { + return pt.x + pt.y; +} + +test "pass by non-copying value through var arg" { + expect(addPointCoordsVar(Point{ .x = 1, .y = 2 }) == 3); +} + +fn addPointCoordsVar(pt: anytype) i32 { + comptime expect(@TypeOf(pt) == Point); + return pt.x + pt.y; +} + +test "pass by non-copying value as method" { + var pt = Point2{ .x = 1, .y = 2 }; + expect(pt.addPointCoords() == 3); +} + +const Point2 = struct { + x: i32, + y: i32, + + fn addPointCoords(self: Point2) i32 { + return self.x + self.y; + } +}; + +test "pass by non-copying value as method, which is generic" { + var pt = Point3{ .x = 1, .y = 2 }; + expect(pt.addPointCoords(i32) == 3); +} + +const Point3 = struct { + x: i32, + y: i32, + + fn addPointCoords(self: Point3, comptime T: type) i32 { + return self.x + self.y; + } +}; + +test "pass by non-copying value as method, at comptime" { + comptime { + var pt = Point2{ .x = 1, .y = 2 }; + expect(pt.addPointCoords() == 3); + } +} + +fn outer(y: u32) fn (u32) u32 { + const Y = @TypeOf(y); + const st = struct { + fn get(z: u32) u32 { + return z + @sizeOf(Y); + } + }; + return st.get; +} + +test "return inner function which references comptime variable of outer function" { + var func = outer(10); + expect(func(3) == 7); +} + +test "extern struct with stdcallcc fn pointer" { + const S = extern struct { + ptr: fn () callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32, + + fn foo() callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32 { + return 1234; + } + }; + + var s: S = undefined; + s.ptr = S.foo; + expect(s.ptr() == 1234); +} + +test "implicit cast fn call result to optional in field result" { + const S = struct { + fn entry() void { + var x = Foo{ + .field = optionalPtr(), + }; + expect(x.field.?.* == 999); + } + + const glob: i32 = 999; + + fn optionalPtr() *const i32 { + return &glob; + } + + const Foo = struct { + field: ?*const i32, + }; + }; + S.entry(); + comptime S.entry(); +} + +test "discard the result of a function that returns a struct" { + const S = struct { + fn entry() void { + _ = func(); + } + + fn func() Foo { + return undefined; + } + + const Foo = struct { + a: u64, + b: u64, + }; + }; + S.entry(); + comptime S.entry(); +} + +test "function call with anon list literal" { + const S = struct { + fn doTheTest() void { + consumeVec(.{ 9, 8, 7 }); + } + + fn consumeVec(vec: [3]f32) void { + expect(vec[0] == 9); + expect(vec[1] == 8); + expect(vec[2] == 7); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "ability to give comptime types and non comptime types to same parameter" { + const S = struct { + fn doTheTest() void { + var x: i32 = 1; + expect(foo(x) == 10); + expect(foo(i32) == 20); + } + + fn foo(arg: anytype) i32 { + if (@typeInfo(@TypeOf(arg)) == .Type and arg == i32) return 20; + return 9 + arg; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "function with inferred error set but returning no error" { + const S = struct { + fn foo() !void {} + }; + + const return_ty = @typeInfo(@TypeOf(S.foo)).Fn.return_type.?; + expectEqual(0, @typeInfo(@typeInfo(return_ty).ErrorUnion.error_set).ErrorSet.?.len); +} diff --git a/test/behavior/fn_delegation.zig b/test/behavior/fn_delegation.zig new file mode 100644 index 0000000000..57006805c8 --- /dev/null +++ b/test/behavior/fn_delegation.zig @@ -0,0 +1,39 @@ +const expect = @import("std").testing.expect; + +const Foo = struct { + a: u64 = 10, + + fn one(self: Foo) u64 { + return self.a + 1; + } + + const two = __two; + + fn __two(self: Foo) u64 { + return self.a + 2; + } + + const three = __three; + + const four = custom(Foo, 4); +}; + +fn __three(self: Foo) u64 { + return self.a + 3; +} + +fn custom(comptime T: type, comptime num: u64) fn (T) u64 { + return struct { + fn function(self: T) u64 { + return self.a + num; + } + }.function; +} + +test "fn delegation" { + const foo = Foo{}; + expect(foo.one() == 11); + expect(foo.two() == 12); + expect(foo.three() == 13); + expect(foo.four() == 14); +} diff --git a/test/behavior/fn_in_struct_in_comptime.zig b/test/behavior/fn_in_struct_in_comptime.zig new file mode 100644 index 0000000000..030693ac59 --- /dev/null +++ b/test/behavior/fn_in_struct_in_comptime.zig @@ -0,0 +1,17 @@ +const expect = @import("std").testing.expect; + +fn get_foo() fn (*u8) usize { + comptime { + return struct { + fn func(ptr: *u8) usize { + var u = @ptrToInt(ptr); + return u; + } + }.func; + } +} + +test "define a function in an anonymous struct in comptime" { + const foo = get_foo(); + expect(foo(@intToPtr(*u8, 12345)) == 12345); +} diff --git a/test/behavior/for.zig b/test/behavior/for.zig new file mode 100644 index 0000000000..c49c73802d --- /dev/null +++ b/test/behavior/for.zig @@ -0,0 +1,172 @@ +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..]); + expect(mem.eql(u8, &target, "bcdefgh")); + + for (source) |*c, i| + expect(@TypeOf(c) == *const u8); + for (target) |*c, i| + 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| { + 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| { + 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) |item, index| { + buffer[buf_index] = @intCast(u8, index); + buf_index += 1; + } + + expect(mem.eql(u8, buffer[0..buf_index], &expected_result)); +} + +test "break from outer for loop" { + testBreakOuter(); + comptime testBreakOuter(); +} + +fn testBreakOuter() void { + var array = "aoeu"; + var count: usize = 0; + outer: for (array) |_| { + for (array) |_| { + count += 1; + break :outer; + } + } + expect(count == 1); +} + +test "continue outer for loop" { + testContinueOuter(); + comptime testContinueOuter(); +} + +fn testContinueOuter() void { + var array = "aoeu"; + var counter: usize = 0; + outer: for (array) |_| { + for (array) |_| { + counter += 1; + continue :outer; + } + } + 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| { + if (f) break false; + if (t) break true; + } else false; + expect(ok); + } + }; + S.entry(true, false); + comptime 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| { + @panic("fail"); + } + } + }; + S.doTheTest(&[_]u8{ 1, 2 }); + comptime 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; + expectEqual(value, i + 1); + } + } + }; + S.doTheTest(); + comptime 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| expect(x == i + 1); + for (ptr) |*x, i| expect(x.* == i + 1); + } + }; + S.doTheTest(&[_]u8{ 1, 2, 3, 4 }); + comptime S.doTheTest(&[_]u8{ 1, 2, 3, 4 }); +} diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig new file mode 100644 index 0000000000..ee6496bcaa --- /dev/null +++ b/test/behavior/generics.zig @@ -0,0 +1,169 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "simple generic fn" { + expect(max(i32, 3, -1) == 3); + expect(max(f32, 0.123, 0.456) == 0.456); + expect(add(2, 3) == 5); +} + +fn max(comptime T: type, a: T, b: T) T { + return if (a > b) a else b; +} + +fn add(comptime a: i32, b: i32) i32 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +test "fn with comptime args" { + expect(gimmeTheBigOne(1234, 5678) == 5678); + expect(shouldCallSameInstance(34, 12) == 34); + expect(sameButWithFloats(0.43, 0.49) == 0.49); +} + +test "var params" { + expect(max_i32(12, 34) == 34); + expect(max_f64(1.2, 3.4) == 3.4); +} + +comptime { + expect(max_i32(12, 34) == 34); + expect(max_f64(1.2, 3.4) == 3.4); +} + +fn max_var(a: anytype, b: anytype) @TypeOf(a + b) { + return if (a > b) a else b; +} + +fn max_i32(a: i32, b: i32) i32 { + return max_var(a, b); +} + +fn max_f64(a: f64, b: f64) f64 { + return max_var(a, b); +} + +pub fn List(comptime T: type) type { + return SmallList(T, 8); +} + +pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type { + return struct { + items: []T, + length: usize, + prealloc_items: [STATIC_SIZE]T, + }; +} + +test "function with return type type" { + var list: List(i32) = undefined; + var list2: List(i32) = undefined; + list.length = 10; + list2.length = 10; + expect(list.prealloc_items.len == 8); + expect(list2.prealloc_items.len == 8); +} + +test "generic struct" { + var a1 = GenNode(i32){ + .value = 13, + .next = null, + }; + var b1 = GenNode(bool){ + .value = true, + .next = null, + }; + expect(a1.value == 13); + expect(a1.value == a1.getVal()); + expect(b1.getVal()); +} +fn GenNode(comptime T: type) type { + return struct { + value: T, + next: ?*GenNode(T), + fn getVal(n: *const GenNode(T)) T { + return n.value; + } + }; +} + +test "const decls in struct" { + expect(GenericDataThing(3).count_plus_one == 4); +} +fn GenericDataThing(comptime count: isize) type { + return struct { + const count_plus_one = count + 1; + }; +} + +test "use generic param in generic param" { + expect(aGenericFn(i32, 3, 4) == 7); +} +fn aGenericFn(comptime T: type, comptime a: T, b: T) T { + return a + b; +} + +test "generic fn with implicit cast" { + expect(getFirstByte(u8, &[_]u8{13}) == 13); + expect(getFirstByte(u16, &[_]u16{ + 0, + 13, + }) == 0); +} +fn getByte(ptr: ?*const u8) u8 { + return ptr.?.*; +} +fn getFirstByte(comptime T: type, mem: []const T) u8 { + return getByte(@ptrCast(*const u8, &mem[0])); +} + +const foos = [_]fn (anytype) bool{ + foo1, + foo2, +}; + +fn foo1(arg: anytype) bool { + return arg; +} +fn foo2(arg: anytype) bool { + return !arg; +} + +test "array of generic fns" { + expect(foos[0](true)); + expect(!foos[1](true)); +} + +test "generic fn keeps non-generic parameter types" { + const A = 128; + + const S = struct { + fn f(comptime T: type, s: []T) void { + expect(A != @typeInfo(@TypeOf(s)).Pointer.alignment); + } + }; + + // The compiler monomorphizes `S.f` for `T=u8` on its first use, check that + // `x` type not affect `s` parameter type. + var x: [16]u8 align(A) = undefined; + S.f(u8, &x); +} diff --git a/test/behavior/hasdecl.zig b/test/behavior/hasdecl.zig new file mode 100644 index 0000000000..f3bb9887fe --- /dev/null +++ b/test/behavior/hasdecl.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const Foo = @import("hasdecl/foo.zig"); + +const Bar = struct { + nope: i32, + + const hi = 1; + pub var blah = "xxx"; +}; + +test "@hasDecl" { + expect(@hasDecl(Foo, "public_thing")); + expect(!@hasDecl(Foo, "private_thing")); + expect(!@hasDecl(Foo, "no_thing")); + + expect(@hasDecl(Bar, "hi")); + expect(@hasDecl(Bar, "blah")); + expect(!@hasDecl(Bar, "nope")); +} diff --git a/test/behavior/hasdecl/foo.zig b/test/behavior/hasdecl/foo.zig new file mode 100644 index 0000000000..c42faaa890 --- /dev/null +++ b/test/behavior/hasdecl/foo.zig @@ -0,0 +1,2 @@ +pub const public_thing = 42; +const private_thing = 666; diff --git a/test/behavior/hasfield.zig b/test/behavior/hasfield.zig new file mode 100644 index 0000000000..c179fedd56 --- /dev/null +++ b/test/behavior/hasfield.zig @@ -0,0 +1,37 @@ +const expect = @import("std").testing.expect; +const builtin = @import("builtin"); + +test "@hasField" { + const struc = struct { + a: i32, + b: []u8, + + pub const nope = 1; + }; + expect(@hasField(struc, "a") == true); + expect(@hasField(struc, "b") == true); + expect(@hasField(struc, "non-existant") == false); + expect(@hasField(struc, "nope") == false); + + const unin = union { + a: u64, + b: []u16, + + pub const nope = 1; + }; + expect(@hasField(unin, "a") == true); + expect(@hasField(unin, "b") == true); + expect(@hasField(unin, "non-existant") == false); + expect(@hasField(unin, "nope") == false); + + const enm = enum { + a, + b, + + pub const nope = 1; + }; + expect(@hasField(enm, "a") == true); + expect(@hasField(enm, "b") == true); + expect(@hasField(enm, "non-existant") == false); + expect(@hasField(enm, "nope") == false); +} diff --git a/test/behavior/if.zig b/test/behavior/if.zig new file mode 100644 index 0000000000..de8a29c76d --- /dev/null +++ b/test/behavior/if.zig @@ -0,0 +1,109 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +test "if statements" { + shouldBeEqual(1, 1); + firstEqlThird(2, 1, 2); +} +fn shouldBeEqual(a: i32, b: i32) void { + if (a != b) { + unreachable; + } else { + return; + } +} +fn firstEqlThird(a: i32, b: i32, c: i32) void { + if (a == b) { + unreachable; + } else if (b == c) { + unreachable; + } else if (a == c) { + return; + } else { + unreachable; + } +} + +test "else if expression" { + expect(elseIfExpressionF(1) == 1); +} +fn elseIfExpressionF(c: u8) u8 { + if (c == 0) { + return 0; + } else if (c == 1) { + return 1; + } else { + return @as(u8, 2); + } +} + +// #2297 +var global_with_val: anyerror!u32 = 0; +var global_with_err: anyerror!u32 = error.SomeError; + +test "unwrap mutable global var" { + if (global_with_val) |v| { + expect(v == 0); + } else |e| { + unreachable; + } + if (global_with_err) |_| { + unreachable; + } else |e| { + expect(e == error.SomeError); + } +} + +test "labeled break inside comptime if inside runtime if" { + var answer: i32 = 0; + var c = true; + if (c) { + answer = if (true) blk: { + break :blk @as(i32, 42); + }; + } + expect(answer == 42); +} + +test "const result loc, runtime if cond, else unreachable" { + const Num = enum { + One, + Two, + }; + + var t = true; + const x = if (t) Num.Two else unreachable; + expect(x == .Two); +} + +test "if prongs cast to expected type instead of peer type resolution" { + const S = struct { + fn doTheTest(f: bool) void { + var x: i32 = 0; + x = if (f) 1 else 2; + expect(x == 2); + + var b = true; + const y: i32 = if (b) 1 else 2; + expect(y == 1); + } + }; + S.doTheTest(false); + comptime 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; + expectEqual(@as(i32, 10), value); + } else unreachable; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/import.zig b/test/behavior/import.zig new file mode 100644 index 0000000000..30655554bf --- /dev/null +++ b/test/behavior/import.zig @@ -0,0 +1,22 @@ +const expect = @import("std").testing.expect; +const expectEqual = @import("std").testing.expectEqual; +const a_namespace = @import("import/a_namespace.zig"); + +test "call fn via namespace lookup" { + expectEqual(@as(i32, 1234), a_namespace.foo()); +} + +test "importing the same thing gives the same import" { + expect(@import("std") == @import("std")); +} + +test "import in non-toplevel scope" { + const S = struct { + usingnamespace @import("import/a_namespace.zig"); + }; + expectEqual(@as(i32, 1234), S.foo()); +} + +test "import empty file" { + const empty = @import("import/empty.zig"); +} diff --git a/test/behavior/import/a_namespace.zig b/test/behavior/import/a_namespace.zig new file mode 100644 index 0000000000..042f1867a5 --- /dev/null +++ b/test/behavior/import/a_namespace.zig @@ -0,0 +1,3 @@ +pub fn foo() i32 { + return 1234; +} diff --git a/test/behavior/import/empty.zig b/test/behavior/import/empty.zig new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/behavior/import/empty.zig diff --git a/test/behavior/incomplete_struct_param_tld.zig b/test/behavior/incomplete_struct_param_tld.zig new file mode 100644 index 0000000000..77a3dfd221 --- /dev/null +++ b/test/behavior/incomplete_struct_param_tld.zig @@ -0,0 +1,30 @@ +const expect = @import("std").testing.expect; + +const A = struct { + b: B, +}; + +const B = struct { + c: C, +}; + +const C = struct { + x: i32, + + fn d(c: *const C) i32 { + return c.x; + } +}; + +fn foo(a: A) i32 { + return a.b.c.d(); +} + +test "incomplete struct param top level declaration" { + const a = A{ + .b = B{ + .c = C{ .x = 13 }, + }, + }; + expect(foo(a) == 13); +} diff --git a/test/behavior/inttoptr.zig b/test/behavior/inttoptr.zig new file mode 100644 index 0000000000..b1780f93d6 --- /dev/null +++ b/test/behavior/inttoptr.zig @@ -0,0 +1,26 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const expect = std.testing.expect; + +test "casting random address to function pointer" { + randomAddressToFunction(); + comptime randomAddressToFunction(); +} + +fn randomAddressToFunction() void { + var addr: usize = 0xdeadbeef; + var ptr = @intToPtr(fn () void, addr); +} + +test "mutate through ptr initialized with constant intToPtr value" { + forceCompilerAnalyzeBranchHardCodedPtrDereference(false); +} + +fn forceCompilerAnalyzeBranchHardCodedPtrDereference(x: bool) void { + const hardCodedP = @intToPtr(*volatile u8, 0xdeadbeef); + if (x) { + hardCodedP.* = hardCodedP.* | 10; + } else { + return; + } +} diff --git a/test/behavior/ir_block_deps.zig b/test/behavior/ir_block_deps.zig new file mode 100644 index 0000000000..821079df79 --- /dev/null +++ b/test/behavior/ir_block_deps.zig @@ -0,0 +1,21 @@ +const expect = @import("std").testing.expect; + +fn foo(id: u64) !i32 { + return switch (id) { + 1 => getErrInt(), + 2 => { + const size = try getErrInt(); + return try getErrInt(); + }, + else => error.ItBroke, + }; +} + +fn getErrInt() anyerror!i32 { + return 0; +} + +test "ir block deps" { + expect((foo(1) catch unreachable) == 0); + expect((foo(2) catch unreachable) == 0); +} diff --git a/test/behavior/math.zig b/test/behavior/math.zig new file mode 100644 index 0000000000..32f4842702 --- /dev/null +++ b/test/behavior/math.zig @@ -0,0 +1,872 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const mem = std.mem; + +test "division" { + testDivision(); + comptime testDivision(); +} +fn testDivision() void { + expect(div(u32, 13, 3) == 4); + expect(div(f16, 1.0, 2.0) == 0.5); + expect(div(f32, 1.0, 2.0) == 0.5); + + expect(divExact(u32, 55, 11) == 5); + expect(divExact(i32, -55, 11) == -5); + expect(divExact(f16, 55.0, 11.0) == 5.0); + expect(divExact(f16, -55.0, 11.0) == -5.0); + expect(divExact(f32, 55.0, 11.0) == 5.0); + expect(divExact(f32, -55.0, 11.0) == -5.0); + + expect(divFloor(i32, 5, 3) == 1); + expect(divFloor(i32, -5, 3) == -2); + expect(divFloor(f16, 5.0, 3.0) == 1.0); + expect(divFloor(f16, -5.0, 3.0) == -2.0); + expect(divFloor(f32, 5.0, 3.0) == 1.0); + expect(divFloor(f32, -5.0, 3.0) == -2.0); + expect(divFloor(i32, -0x80000000, -2) == 0x40000000); + expect(divFloor(i32, 0, -0x80000000) == 0); + expect(divFloor(i32, -0x40000001, 0x40000000) == -2); + expect(divFloor(i32, -0x80000000, 1) == -0x80000000); + expect(divFloor(i32, 10, 12) == 0); + expect(divFloor(i32, -14, 12) == -2); + expect(divFloor(i32, -2, 12) == -1); + + expect(divTrunc(i32, 5, 3) == 1); + expect(divTrunc(i32, -5, 3) == -1); + expect(divTrunc(f16, 5.0, 3.0) == 1.0); + expect(divTrunc(f16, -5.0, 3.0) == -1.0); + expect(divTrunc(f32, 5.0, 3.0) == 1.0); + expect(divTrunc(f32, -5.0, 3.0) == -1.0); + expect(divTrunc(f64, 5.0, 3.0) == 1.0); + expect(divTrunc(f64, -5.0, 3.0) == -1.0); + expect(divTrunc(i32, 10, 12) == 0); + expect(divTrunc(i32, -14, 12) == -1); + expect(divTrunc(i32, -2, 12) == 0); + + expect(mod(i32, 10, 12) == 10); + expect(mod(i32, -14, 12) == 10); + expect(mod(i32, -2, 12) == 10); + + comptime { + expect( + 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, + ); + expect( + @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, + ); + expect( + 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, + ); + expect( + @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, + ); + expect( + @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, + ); + expect( + @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, + ); + expect( + 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, + ); + } +} +fn div(comptime T: type, a: T, b: T) T { + return a / b; +} +fn divExact(comptime T: type, a: T, b: T) T { + return @divExact(a, b); +} +fn divFloor(comptime T: type, a: T, b: T) T { + return @divFloor(a, b); +} +fn divTrunc(comptime T: type, a: T, b: T) T { + return @divTrunc(a, b); +} +fn mod(comptime T: type, a: T, b: T) T { + return @mod(a, b); +} + +test "@addWithOverflow" { + var result: u8 = undefined; + expect(@addWithOverflow(u8, 250, 100, &result)); + expect(!@addWithOverflow(u8, 100, 150, &result)); + expect(result == 250); +} + +// TODO test mulWithOverflow +// TODO test subWithOverflow + +test "@shlWithOverflow" { + var result: u16 = undefined; + expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result)); + expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result)); + expect(result == 0b1011111111111100); +} + +test "@*WithOverflow with u0 values" { + var result: u0 = undefined; + expect(!@addWithOverflow(u0, 0, 0, &result)); + expect(!@subWithOverflow(u0, 0, 0, &result)); + expect(!@mulWithOverflow(u0, 0, 0, &result)); + expect(!@shlWithOverflow(u0, 0, 0, &result)); +} + +test "@clz" { + testClz(); + comptime testClz(); +} + +fn testClz() void { + expect(clz(u8, 0b10001010) == 0); + expect(clz(u8, 0b00001010) == 4); + expect(clz(u8, 0b00011010) == 3); + expect(clz(u8, 0b00000000) == 8); + expect(clz(u128, 0xffffffffffffffff) == 64); + expect(clz(u128, 0x10000000000000000) == 63); +} + +fn clz(comptime T: type, x: T) usize { + return @clz(T, x); +} + +test "@ctz" { + testCtz(); + comptime testCtz(); +} + +fn testCtz() void { + expect(ctz(u8, 0b10100000) == 5); + expect(ctz(u8, 0b10001010) == 1); + expect(ctz(u8, 0b00000000) == 8); + expect(ctz(u16, 0b00000000) == 16); +} + +fn ctz(comptime T: type, x: T) usize { + return @ctz(T, x); +} + +test "assignment operators" { + var i: u32 = 0; + i += 5; + expect(i == 5); + i -= 2; + expect(i == 3); + i *= 20; + expect(i == 60); + i /= 3; + expect(i == 20); + i %= 11; + expect(i == 9); + i <<= 1; + expect(i == 18); + i >>= 2; + expect(i == 4); + i = 6; + i &= 5; + expect(i == 4); + i ^= 6; + expect(i == 2); + i = 6; + i |= 3; + expect(i == 7); +} + +test "three expr in a row" { + testThreeExprInARow(false, true); + comptime testThreeExprInARow(false, true); +} +fn testThreeExprInARow(f: bool, t: bool) void { + assertFalse(f or f or f); + assertFalse(t and t and f); + assertFalse(1 | 2 | 4 != 7); + assertFalse(3 ^ 6 ^ 8 != 13); + assertFalse(7 & 14 & 28 != 4); + assertFalse(9 << 1 << 2 != 9 << 3); + assertFalse(90 >> 1 >> 2 != 90 >> 3); + assertFalse(100 - 1 + 1000 != 1099); + assertFalse(5 * 4 / 2 % 3 != 1); + assertFalse(@as(i32, @as(i32, 5)) != 5); + assertFalse(!!false); + assertFalse(@as(i32, 7) != --(@as(i32, 7))); +} +fn assertFalse(b: bool) void { + expect(!b); +} + +test "const number literal" { + const one = 1; + const eleven = ten + one; + + expect(eleven == 11); +} +const ten = 10; + +test "unsigned wrapping" { + testUnsignedWrappingEval(maxInt(u32)); + comptime testUnsignedWrappingEval(maxInt(u32)); +} +fn testUnsignedWrappingEval(x: u32) void { + const zero = x +% 1; + expect(zero == 0); + const orig = zero -% 1; + expect(orig == maxInt(u32)); +} + +test "signed wrapping" { + testSignedWrappingEval(maxInt(i32)); + comptime testSignedWrappingEval(maxInt(i32)); +} +fn testSignedWrappingEval(x: i32) void { + const min_val = x +% 1; + expect(min_val == minInt(i32)); + const max_val = min_val -% 1; + expect(max_val == maxInt(i32)); +} + +test "signed negation wrapping" { + testSignedNegationWrappingEval(minInt(i16)); + comptime testSignedNegationWrappingEval(minInt(i16)); +} +fn testSignedNegationWrappingEval(x: i16) void { + expect(x == -32768); + const neg = -%x; + expect(neg == -32768); +} + +test "unsigned negation wrapping" { + testUnsignedNegationWrappingEval(1); + comptime testUnsignedNegationWrappingEval(1); +} +fn testUnsignedNegationWrappingEval(x: u16) void { + expect(x == 1); + const neg = -%x; + expect(neg == maxInt(u16)); +} + +test "unsigned 64-bit division" { + test_u64_div(); + comptime test_u64_div(); +} +fn test_u64_div() void { + const result = divWithResult(1152921504606846976, 34359738365); + expect(result.quotient == 33554432); + expect(result.remainder == 100663296); +} +fn divWithResult(a: u64, b: u64) DivResult { + return DivResult{ + .quotient = a / b, + .remainder = a % b, + }; +} +const DivResult = struct { + quotient: u64, + remainder: u64, +}; + +test "binary not" { + expect(comptime x: { + break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101; + }); + expect(comptime x: { + break :x ~@as(u64, 2147483647) == 18446744071562067968; + }); + testBinaryNot(0b1010101010101010); +} + +fn testBinaryNot(x: u16) void { + expect(~x == 0b0101010101010101); +} + +test "small int addition" { + var x: u2 = 0; + expect(x == 0); + + x += 1; + expect(x == 1); + + x += 1; + expect(x == 2); + + x += 1; + expect(x == 3); + + var result: @TypeOf(x) = 3; + expect(@addWithOverflow(@TypeOf(x), x, 1, &result)); + + expect(result == 0); +} + +test "float equality" { + const x: f64 = 0.012; + const y: f64 = x + 1.0; + + testFloatEqualityImpl(x, y); + comptime testFloatEqualityImpl(x, y); +} + +fn testFloatEqualityImpl(x: f64, y: f64) void { + const y2 = x + 1.0; + expect(y == y2); +} + +test "allow signed integer division/remainder when values are comptime known and positive or exact" { + expect(5 / 3 == 1); + expect(-5 / -3 == 1); + expect(-6 / 3 == -2); + + expect(5 % 3 == 2); + expect(-6 % 3 == 0); +} + +test "hex float literal parsing" { + comptime expect(0x1.0 == 1.0); +} + +test "quad hex float literal parsing in range" { + const a = 0x1.af23456789bbaaab347645365cdep+5; + const b = 0x1.dedafcff354b6ae9758763545432p-9; + const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; + const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; +} + +test "quad hex float literal parsing accurate" { + const a: f128 = 0x1.1111222233334444555566667777p+0; + + // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing. + const expected: u128 = 0x3fff1111222233334444555566667777; + expect(@bitCast(u128, a) == expected); + + // non-normalized + const b: f128 = 0x11.111222233334444555566667777p-4; + expect(@bitCast(u128, b) == expected); + + const S = struct { + fn doTheTest() void { + { + var f: f128 = 0x1.2eab345678439abcdefea56782346p+5; + expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234); + } + { + var f: f128 = 0x1.edcb34a235253948765432134674fp-1; + expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674); + } + { + var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50; + expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50); + } + { + var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9; + expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568); + } + const exp2ft = [_]f64{ + 0x1.6a09e667f3bcdp-1, + 0x1.7a11473eb0187p-1, + 0x1.8ace5422aa0dbp-1, + 0x1.9c49182a3f090p-1, + 0x1.ae89f995ad3adp-1, + 0x1.c199bdd85529cp-1, + 0x1.d5818dcfba487p-1, + 0x1.ea4afa2a490dap-1, + 0x1.0000000000000p+0, + 0x1.0b5586cf9890fp+0, + 0x1.172b83c7d517bp+0, + 0x1.2387a6e756238p+0, + 0x1.306fe0a31b715p+0, + 0x1.3dea64c123422p+0, + 0x1.4bfdad5362a27p+0, + 0x1.5ab07dd485429p+0, + 0x1.8p23, + 0x1.62e430p-1, + 0x1.ebfbe0p-3, + 0x1.c6b348p-5, + 0x1.3b2c9cp-7, + 0x1.0p127, + -0x1.0p-149, + }; + + const answers = [_]u64{ + 0x3fe6a09e667f3bcd, + 0x3fe7a11473eb0187, + 0x3fe8ace5422aa0db, + 0x3fe9c49182a3f090, + 0x3feae89f995ad3ad, + 0x3fec199bdd85529c, + 0x3fed5818dcfba487, + 0x3feea4afa2a490da, + 0x3ff0000000000000, + 0x3ff0b5586cf9890f, + 0x3ff172b83c7d517b, + 0x3ff2387a6e756238, + 0x3ff306fe0a31b715, + 0x3ff3dea64c123422, + 0x3ff4bfdad5362a27, + 0x3ff5ab07dd485429, + 0x4168000000000000, + 0x3fe62e4300000000, + 0x3fcebfbe00000000, + 0x3fac6b3480000000, + 0x3f83b2c9c0000000, + 0x47e0000000000000, + 0xb6a0000000000000, + }; + + for (exp2ft) |x, i| { + expect(@bitCast(u64, x) == answers[i]); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "underscore separator parsing" { + expect(0_0_0_0 == 0); + expect(1_234_567 == 1234567); + expect(001_234_567 == 1234567); + expect(0_0_1_2_3_4_5_6_7 == 1234567); + + expect(0b0_0_0_0 == 0); + expect(0b1010_1010 == 0b10101010); + expect(0b0000_1010_1010 == 0b10101010); + expect(0b1_0_1_0_1_0_1_0 == 0b10101010); + + expect(0o0_0_0_0 == 0); + expect(0o1010_1010 == 0o10101010); + expect(0o0000_1010_1010 == 0o10101010); + expect(0o1_0_1_0_1_0_1_0 == 0o10101010); + + expect(0x0_0_0_0 == 0); + expect(0x1010_1010 == 0x10101010); + expect(0x0000_1010_1010 == 0x10101010); + expect(0x1_0_1_0_1_0_1_0 == 0x10101010); + + expect(123_456.789_000e1_0 == 123456.789000e10); + expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); + + expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); + expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); +} + +test "hex float literal within range" { + const a = 0x1.0p16383; + const b = 0x0.1p16387; + const c = 0x1.0p-16382; +} + +test "truncating shift left" { + testShlTrunc(maxInt(u16)); + comptime testShlTrunc(maxInt(u16)); +} +fn testShlTrunc(x: u16) void { + const shifted = x << 1; + expect(shifted == 65534); +} + +test "truncating shift right" { + testShrTrunc(maxInt(u16)); + comptime testShrTrunc(maxInt(u16)); +} +fn testShrTrunc(x: u16) void { + const shifted = x >> 1; + expect(shifted == 32767); +} + +test "exact shift left" { + testShlExact(0b00110101); + comptime testShlExact(0b00110101); +} +fn testShlExact(x: u8) void { + const shifted = @shlExact(x, 2); + expect(shifted == 0b11010100); +} + +test "exact shift right" { + testShrExact(0b10110100); + comptime testShrExact(0b10110100); +} +fn testShrExact(x: u8) void { + const shifted = @shrExact(x, 2); + expect(shifted == 0b00101101); +} + +test "shift left/right on u0 operand" { + const S = struct { + fn doTheTest() void { + var x: u0 = 0; + var y: u0 = 0; + expectEqual(@as(u0, 0), x << 0); + expectEqual(@as(u0, 0), x >> 0); + expectEqual(@as(u0, 0), x << y); + expectEqual(@as(u0, 0), x >> y); + expectEqual(@as(u0, 0), @shlExact(x, 0)); + expectEqual(@as(u0, 0), @shrExact(x, 0)); + expectEqual(@as(u0, 0), @shlExact(x, y)); + expectEqual(@as(u0, 0), @shrExact(x, y)); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "comptime_int addition" { + comptime { + expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); + expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); + } +} + +test "comptime_int multiplication" { + comptime { + expect( + 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, + ); + expect( + 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, + ); + } +} + +test "comptime_int shifting" { + comptime { + expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); + } +} + +test "comptime_int multi-limb shift and mask" { + comptime { + var a = 0xefffffffa0000001eeeeeeefaaaaaaab; + + expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); + a >>= 32; + expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); + a >>= 32; + expect(@as(u32, a & 0xffffffff) == 0xa0000001); + a >>= 32; + expect(@as(u32, a & 0xffffffff) == 0xefffffff); + a >>= 32; + + expect(a == 0); + } +} + +test "comptime_int multi-limb partial shift right" { + comptime { + var a = 0x1ffffffffeeeeeeee; + a >>= 16; + expect(a == 0x1ffffffffeeee); + } +} + +test "xor" { + test_xor(); + comptime test_xor(); +} + +fn test_xor() void { + expect(0xFF ^ 0x00 == 0xFF); + expect(0xF0 ^ 0x0F == 0xFF); + expect(0xFF ^ 0xF0 == 0x0F); + expect(0xFF ^ 0x0F == 0xF0); + expect(0xFF ^ 0xFF == 0x00); +} + +test "comptime_int xor" { + comptime { + expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); + expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); + expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); + expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); + expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); + } +} + +test "f128" { + test_f128(); + comptime test_f128(); +} + +fn make_f128(x: f128) f128 { + return x; +} + +fn test_f128() void { + expect(@sizeOf(f128) == 16); + expect(make_f128(1.0) == 1.0); + expect(make_f128(1.0) != 1.1); + expect(make_f128(1.0) > 0.9); + expect(make_f128(1.0) >= 0.9); + expect(make_f128(1.0) >= 1.0); + should_not_be_zero(1.0); +} + +fn should_not_be_zero(x: f128) void { + expect(x != 0.0); +} + +test "comptime float rem int" { + comptime { + var x = @as(f32, 1) % 2; + expect(x == 1.0); + } +} + +test "remainder division" { + comptime remdiv(f16); + comptime remdiv(f32); + comptime remdiv(f64); + comptime remdiv(f128); + remdiv(f16); + remdiv(f64); + remdiv(f128); +} + +fn remdiv(comptime T: type) void { + expect(@as(T, 1) == @as(T, 1) % @as(T, 2)); + expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); +} + +test "@sqrt" { + testSqrt(f64, 12.0); + comptime testSqrt(f64, 12.0); + testSqrt(f32, 13.0); + comptime testSqrt(f32, 13.0); + testSqrt(f16, 13.0); + comptime testSqrt(f16, 13.0); + + const x = 14.0; + const y = x * x; + const z = @sqrt(y); + comptime expect(z == x); +} + +fn testSqrt(comptime T: type, x: T) void { + expect(@sqrt(x * x) == x); +} + +test "@fabs" { + testFabs(f128, 12.0); + comptime testFabs(f128, 12.0); + testFabs(f64, 12.0); + comptime testFabs(f64, 12.0); + testFabs(f32, 12.0); + comptime testFabs(f32, 12.0); + testFabs(f16, 12.0); + comptime testFabs(f16, 12.0); + + const x = 14.0; + const y = -x; + const z = @fabs(y); + comptime expectEqual(x, z); +} + +fn testFabs(comptime T: type, x: T) void { + const y = -x; + const z = @fabs(y); + expectEqual(x, z); +} + +test "@floor" { + // FIXME: Generates a floorl function call + // testFloor(f128, 12.0); + comptime testFloor(f128, 12.0); + testFloor(f64, 12.0); + comptime testFloor(f64, 12.0); + testFloor(f32, 12.0); + comptime testFloor(f32, 12.0); + testFloor(f16, 12.0); + comptime testFloor(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @floor(y); + comptime expectEqual(x, z); +} + +fn testFloor(comptime T: type, x: T) void { + const y = x + 0.6; + const z = @floor(y); + expectEqual(x, z); +} + +test "@ceil" { + // FIXME: Generates a ceill function call + //testCeil(f128, 12.0); + comptime testCeil(f128, 12.0); + testCeil(f64, 12.0); + comptime testCeil(f64, 12.0); + testCeil(f32, 12.0); + comptime testCeil(f32, 12.0); + testCeil(f16, 12.0); + comptime testCeil(f16, 12.0); + + const x = 14.0; + const y = x - 0.7; + const z = @ceil(y); + comptime expectEqual(x, z); +} + +fn testCeil(comptime T: type, x: T) void { + const y = x - 0.8; + const z = @ceil(y); + expectEqual(x, z); +} + +test "@trunc" { + // FIXME: Generates a truncl function call + //testTrunc(f128, 12.0); + comptime testTrunc(f128, 12.0); + testTrunc(f64, 12.0); + comptime testTrunc(f64, 12.0); + testTrunc(f32, 12.0); + comptime testTrunc(f32, 12.0); + testTrunc(f16, 12.0); + comptime testTrunc(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @trunc(y); + comptime expectEqual(x, z); +} + +fn testTrunc(comptime T: type, x: T) void { + { + const y = x + 0.8; + const z = @trunc(y); + expectEqual(x, z); + } + + { + const y = -x - 0.8; + const z = @trunc(y); + expectEqual(-x, z); + } +} + +test "@round" { + // FIXME: Generates a roundl function call + //testRound(f128, 12.0); + comptime testRound(f128, 12.0); + testRound(f64, 12.0); + comptime testRound(f64, 12.0); + testRound(f32, 12.0); + comptime testRound(f32, 12.0); + testRound(f16, 12.0); + comptime testRound(f16, 12.0); + + const x = 14.0; + const y = x + 0.4; + const z = @round(y); + comptime expectEqual(x, z); +} + +fn testRound(comptime T: type, x: T) void { + const y = x - 0.5; + const z = @round(y); + expectEqual(x, z); +} + +test "comptime_int param and return" { + const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); + expect(a == 137114567242441932203689521744947848950); + + const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); + expect(b == 985095453608931032642182098849559179469148836107390954364380); +} + +fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { + return a + b; +} + +test "vector integer addition" { + const S = struct { + fn doTheTest() void { + var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; + var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 }; + var result = a + b; + var result_array: [4]i32 = result; + const expected = [_]i32{ 6, 8, 10, 12 }; + expectEqualSlices(i32, &expected, &result_array); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "NaN comparison" { + testNanEqNan(f16); + testNanEqNan(f32); + testNanEqNan(f64); + testNanEqNan(f128); + comptime testNanEqNan(f16); + comptime testNanEqNan(f32); + comptime testNanEqNan(f64); + comptime testNanEqNan(f128); +} + +fn testNanEqNan(comptime F: type) void { + var nan1 = std.math.nan(F); + var nan2 = std.math.nan(F); + expect(nan1 != nan2); + expect(!(nan1 == nan2)); + expect(!(nan1 > nan2)); + expect(!(nan1 >= nan2)); + expect(!(nan1 < nan2)); + expect(!(nan1 <= nan2)); +} + +test "128-bit multiplication" { + var a: i128 = 3; + var b: i128 = 2; + var c = a * b; + expect(c == 6); +} + +test "vector comparison" { + const S = struct { + fn doTheTest() void { + var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 }; + var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 }; + expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false })); + expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false })); + expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false })); + expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true })); + expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true })); + expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "compare undefined literal with comptime_int" { + var x = undefined == 1; + // x is now undefined with type bool + x = true; + expect(x); +} + +test "signed zeros are represented properly" { + const S = struct { + fn doTheTest() void { + inline for ([_]type{ f16, f32, f64, f128 }) |T| { + const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + var as_fp_val = -@as(T, 0.0); + var as_uint_val = @bitCast(ST, as_fp_val); + // Ensure the sign bit is set. + expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); + } + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/merge_error_sets.zig b/test/behavior/merge_error_sets.zig new file mode 100644 index 0000000000..147b580232 --- /dev/null +++ b/test/behavior/merge_error_sets.zig @@ -0,0 +1,21 @@ +const A = error{ + FileNotFound, + NotDir, +}; +const B = error{OutOfMemory}; + +const C = A || B; + +fn foo() C!void { + return error.NotDir; +} + +test "merge error sets" { + if (foo()) { + @panic("unexpected"); + } else |err| switch (err) { + error.OutOfMemory => @panic("unexpected"), + error.FileNotFound => @panic("unexpected"), + error.NotDir => {}, + } +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig new file mode 100644 index 0000000000..850622c5ba --- /dev/null +++ b/test/behavior/misc.zig @@ -0,0 +1,761 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqualSlices = std.testing.expectEqualSlices; +const mem = std.mem; +const builtin = @import("builtin"); + +// normal comment + +/// this is a documentation comment +/// doc comment line 2 +fn emptyFunctionWithComments() void {} + +test "empty function with comments" { + emptyFunctionWithComments(); +} + +comptime { + @export(disabledExternFn, .{ .name = "disabledExternFn", .linkage = .Internal }); +} + +fn disabledExternFn() callconv(.C) void {} + +test "call disabled extern fn" { + disabledExternFn(); +} + +test "short circuit" { + testShortCircuit(false, true); + comptime testShortCircuit(false, true); +} + +fn testShortCircuit(f: bool, t: bool) void { + var hit_1 = f; + var hit_2 = f; + var hit_3 = f; + var hit_4 = f; + + if (t or x: { + expect(f); + break :x f; + }) { + hit_1 = t; + } + if (f or x: { + hit_2 = t; + break :x f; + }) { + expect(f); + } + + if (t and x: { + hit_3 = t; + break :x f; + }) { + expect(f); + } + if (f and x: { + expect(f); + break :x f; + }) { + expect(f); + } else { + hit_4 = t; + } + expect(hit_1); + expect(hit_2); + expect(hit_3); + expect(hit_4); +} + +test "truncate" { + expect(testTruncate(0x10fd) == 0xfd); +} +fn testTruncate(x: u32) u8 { + return @truncate(u8, x); +} + +fn first4KeysOfHomeRow() []const u8 { + return "aoeu"; +} + +test "return string from function" { + expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu")); +} + +const g1: i32 = 1233 + 1; +var g2: i32 = 0; + +test "global variables" { + expect(g2 == 0); + g2 = g1; + expect(g2 == 1234); +} + +test "memcpy and memset intrinsics" { + var foo: [20]u8 = undefined; + var bar: [20]u8 = undefined; + + @memset(&foo, 'A', foo.len); + @memcpy(&bar, &foo, bar.len); + + if (bar[11] != 'A') unreachable; +} + +test "builtin static eval" { + const x: i32 = comptime x: { + break :x 1 + 2 + 3; + }; + expect(x == comptime 6); +} + +test "slicing" { + var array: [20]i32 = undefined; + + array[5] = 1234; + + var slice = array[5..10]; + + if (slice.len != 5) unreachable; + + const ptr = &slice[0]; + if (ptr.* != 1234) unreachable; + + var slice_rest = array[10..]; + if (slice_rest.len != 10) unreachable; +} + +test "constant equal function pointers" { + const alias = emptyFn; + expect(comptime x: { + break :x emptyFn == alias; + }); +} + +fn emptyFn() void {} + +test "hex escape" { + expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello")); +} + +test "string concatenation" { + expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED")); +} + +test "array mult operator" { + expect(mem.eql(u8, "ab" ** 5, "ababababab")); +} + +test "string escapes" { + expect(mem.eql(u8, "\"", "\x22")); + expect(mem.eql(u8, "\'", "\x27")); + expect(mem.eql(u8, "\n", "\x0a")); + expect(mem.eql(u8, "\r", "\x0d")); + expect(mem.eql(u8, "\t", "\x09")); + expect(mem.eql(u8, "\\", "\x5c")); + expect(mem.eql(u8, "\u{1234}\u{069}\u{1}", "\xe1\x88\xb4\x69\x01")); +} + +test "multiline string" { + const s1 = + \\one + \\two) + \\three + ; + const s2 = "one\ntwo)\nthree"; + expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at start" { + const s1 = + //\\one + \\two) + \\three + ; + const s2 = "two)\nthree"; + expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at end" { + const s1 = + \\one + \\two) + //\\three + ; + const s2 = "one\ntwo)"; + expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments in middle" { + const s1 = + \\one + //\\two) + \\three + ; + const s2 = "one\nthree"; + expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at multiple places" { + const s1 = + \\one + //\\two + \\three + //\\four + \\five + ; + const s2 = "one\nthree\nfive"; + expect(mem.eql(u8, s1, s2)); +} + +test "multiline C string" { + const s1 = + \\one + \\two) + \\three + ; + const s2 = "one\ntwo)\nthree"; + expect(std.cstr.cmp(s1, s2) == 0); +} + +test "type equality" { + expect(*const u8 != *u8); +} + +const global_a: i32 = 1234; +const global_b: *const i32 = &global_a; +const global_c: *const f32 = @ptrCast(*const f32, global_b); +test "compile time global reinterpret" { + const d = @ptrCast(*const i32, global_c); + expect(d.* == 1234); +} + +test "explicit cast maybe pointers" { + const a: ?*i32 = undefined; + const b: ?*f32 = @ptrCast(?*f32, a); +} + +test "generic malloc free" { + const a = memAlloc(u8, 10) catch unreachable; + memFree(u8, a); +} +var some_mem: [100]u8 = undefined; +fn memAlloc(comptime T: type, n: usize) anyerror![]T { + return @ptrCast([*]T, &some_mem[0])[0..n]; +} +fn memFree(comptime T: type, memory: []T) void {} + +test "cast undefined" { + const array: [100]u8 = undefined; + const slice = @as([]const u8, &array); + testCastUndefined(slice); +} +fn testCastUndefined(x: []const u8) void {} + +test "cast small unsigned to larger signed" { + expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200)); + expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999)); +} +fn castSmallUnsignedToLargerSigned1(x: u8) i16 { + return x; +} +fn castSmallUnsignedToLargerSigned2(x: u16) i64 { + return x; +} + +test "implicit cast after unreachable" { + expect(outer() == 1234); +} +fn inner() i32 { + return 1234; +} +fn outer() i64 { + return inner(); +} + +test "pointer dereferencing" { + var x = @as(i32, 3); + const y = &x; + + y.* += 1; + + expect(x == 4); + expect(y.* == 4); +} + +test "call result of if else expression" { + expect(mem.eql(u8, f2(true), "a")); + expect(mem.eql(u8, f2(false), "b")); +} +fn f2(x: bool) []const u8 { + return (if (x) fA else fB)(); +} +fn fA() []const u8 { + return "a"; +} +fn fB() []const u8 { + return "b"; +} + +test "const expression eval handling of variables" { + var x = true; + while (x) { + x = false; + } +} + +test "constant enum initialization with differing sizes" { + test3_1(test3_foo); + test3_2(test3_bar); +} +const Test3Foo = union(enum) { + One: void, + Two: f32, + Three: Test3Point, +}; +const Test3Point = struct { + x: i32, + y: i32, +}; +const test3_foo = Test3Foo{ + .Three = Test3Point{ + .x = 3, + .y = 4, + }, +}; +const test3_bar = Test3Foo{ .Two = 13 }; +fn test3_1(f: Test3Foo) void { + switch (f) { + Test3Foo.Three => |pt| { + expect(pt.x == 3); + expect(pt.y == 4); + }, + else => unreachable, + } +} +fn test3_2(f: Test3Foo) void { + switch (f) { + Test3Foo.Two => |x| { + expect(x == 13); + }, + else => unreachable, + } +} + +test "character literals" { + expect('\'' == single_quote); +} +const single_quote = '\''; + +test "take address of parameter" { + testTakeAddressOfParameter(12.34); +} +fn testTakeAddressOfParameter(f: f32) void { + const f_ptr = &f; + expect(f_ptr.* == 12.34); +} + +test "pointer comparison" { + const a = @as([]const u8, "a"); + const b = &a; + expect(ptrEql(b, b)); +} +fn ptrEql(a: *const []const u8, b: *const []const u8) bool { + return a == b; +} + +test "string concatenation" { + const a = "OK" ++ " IT " ++ "WORKED"; + const b = "OK IT WORKED"; + + comptime expect(@TypeOf(a) == *const [12:0]u8); + comptime expect(@TypeOf(b) == *const [12:0]u8); + + const len = mem.len(b); + const len_with_null = len + 1; + { + var i: u32 = 0; + while (i < len_with_null) : (i += 1) { + expect(a[i] == b[i]); + } + } + expect(a[len] == 0); + expect(b[len] == 0); +} + +test "pointer to void return type" { + testPointerToVoidReturnType() catch unreachable; +} +fn testPointerToVoidReturnType() anyerror!void { + const a = testPointerToVoidReturnType2(); + return a.*; +} +const test_pointer_to_void_return_type_x = void{}; +fn testPointerToVoidReturnType2() *const void { + return &test_pointer_to_void_return_type_x; +} + +test "non const ptr to aliased type" { + const int = i32; + expect(?*int == ?*i32); +} + +test "array 2D const double ptr" { + const rect_2d_vertexes = [_][1]f32{ + [_]f32{1.0}, + [_]f32{2.0}, + }; + testArray2DConstDoublePtr(&rect_2d_vertexes[0][0]); +} + +fn testArray2DConstDoublePtr(ptr: *const f32) void { + const ptr2 = @ptrCast([*]const f32, ptr); + expect(ptr2[0] == 1.0); + expect(ptr2[1] == 2.0); +} + +const AStruct = struct { + x: i32, +}; +const AnEnum = enum { + One, + Two, +}; +const AUnionEnum = union(enum) { + One: i32, + Two: void, +}; +const AUnion = union { + One: void, + Two: void, +}; + +test "@typeName" { + const Struct = struct {}; + const Union = union { + unused: u8, + }; + const Enum = enum { + Unused, + }; + comptime { + expect(mem.eql(u8, @typeName(i64), "i64")); + expect(mem.eql(u8, @typeName(*usize), "*usize")); + // https://github.com/ziglang/zig/issues/675 + expect(mem.eql(u8, "behavior.misc.TypeFromFn(u8)", @typeName(TypeFromFn(u8)))); + expect(mem.eql(u8, @typeName(Struct), "Struct")); + expect(mem.eql(u8, @typeName(Union), "Union")); + expect(mem.eql(u8, @typeName(Enum), "Enum")); + } +} + +fn TypeFromFn(comptime T: type) type { + return struct {}; +} + +test "double implicit cast in same expression" { + var x = @as(i32, @as(u16, nine())); + expect(x == 9); +} +fn nine() u8 { + return 9; +} + +test "global variable initialized to global variable array element" { + expect(global_ptr == &gdt[0]); +} +const GDTEntry = struct { + field: i32, +}; +var gdt = [_]GDTEntry{ + GDTEntry{ .field = 1 }, + GDTEntry{ .field = 2 }, +}; +var global_ptr = &gdt[0]; + +// can't really run this test but we can make sure it has no compile error +// and generates code +const vram = @intToPtr([*]volatile u8, 0x20000000)[0..0x8000]; +export fn writeToVRam() void { + vram[0] = 'X'; +} + +const OpaqueA = opaque {}; +const OpaqueB = opaque {}; +test "opaque types" { + expect(*OpaqueA != *OpaqueB); + expect(mem.eql(u8, @typeName(OpaqueA), "OpaqueA")); + expect(mem.eql(u8, @typeName(OpaqueB), "OpaqueB")); +} + +test "variable is allowed to be a pointer to an opaque type" { + var x: i32 = 1234; + _ = hereIsAnOpaqueType(@ptrCast(*OpaqueA, &x)); +} +fn hereIsAnOpaqueType(ptr: *OpaqueA) *OpaqueA { + var a = ptr; + return a; +} + +test "comptime if inside runtime while which unconditionally breaks" { + testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true); + comptime testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true); +} +fn testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(cond: bool) void { + while (cond) { + if (false) {} + break; + } +} + +test "implicit comptime while" { + while (false) { + @compileError("bad"); + } +} + +fn fnThatClosesOverLocalConst() type { + const c = 1; + return struct { + fn g() i32 { + return c; + } + }; +} + +test "function closes over local const" { + const x = fnThatClosesOverLocalConst().g(); + expect(x == 1); +} + +test "cold function" { + thisIsAColdFn(); + comptime thisIsAColdFn(); +} + +fn thisIsAColdFn() void { + @setCold(true); +} + +const PackedStruct = packed struct { + a: u8, + b: u8, +}; +const PackedUnion = packed union { + a: u8, + b: u32, +}; +const PackedEnum = packed enum { + A, + B, +}; + +test "packed struct, enum, union parameters in extern function" { + testPackedStuff(&(PackedStruct{ + .a = 1, + .b = 2, + }), &(PackedUnion{ .a = 1 }), PackedEnum.A); +} + +export fn testPackedStuff(a: *const PackedStruct, b: *const PackedUnion, c: PackedEnum) void {} + +test "slicing zero length array" { + const s1 = ""[0..]; + const s2 = ([_]u32{})[0..]; + expect(s1.len == 0); + expect(s2.len == 0); + expect(mem.eql(u8, s1, "")); + expect(mem.eql(u32, s2, &[_]u32{})); +} + +const addr1 = @ptrCast(*const u8, emptyFn); +test "comptime cast fn to ptr" { + const addr2 = @ptrCast(*const u8, emptyFn); + comptime expect(addr1 == addr2); +} + +test "equality compare fn ptrs" { + var a = emptyFn; + expect(a == a); +} + +test "self reference through fn ptr field" { + const S = struct { + const A = struct { + f: fn (A) u8, + }; + + fn foo(a: A) u8 { + return 12; + } + }; + var a: S.A = undefined; + a.f = S.foo; + expect(a.f(a) == 12); +} + +test "volatile load and store" { + var number: i32 = 1234; + const ptr = @as(*volatile i32, &number); + ptr.* += 1; + expect(ptr.* == 1235); +} + +test "slice string literal has correct type" { + comptime { + expect(@TypeOf("aoeu"[0..]) == *const [4:0]u8); + const array = [_]i32{ 1, 2, 3, 4 }; + expect(@TypeOf(array[0..]) == *const [4]i32); + } + var runtime_zero: usize = 0; + comptime expect(@TypeOf("aoeu"[runtime_zero..]) == [:0]const u8); + const array = [_]i32{ 1, 2, 3, 4 }; + comptime expect(@TypeOf(array[runtime_zero..]) == []const i32); +} + +test "struct inside function" { + testStructInFn(); + comptime testStructInFn(); +} + +fn testStructInFn() void { + const BlockKind = u32; + + const Block = struct { + kind: BlockKind, + }; + + var block = Block{ .kind = 1234 }; + + block.kind += 1; + + expect(block.kind == 1235); +} + +test "fn call returning scalar optional in equality expression" { + expect(getNull() == null); +} + +fn getNull() ?*i32 { + return null; +} + +test "thread local variable" { + const S = struct { + threadlocal var t: i32 = 1234; + }; + S.t += 1; + expect(S.t == 1235); +} + +test "unicode escape in character literal" { + var a: u24 = '\u{01f4a9}'; + expect(a == 128169); +} + +test "unicode character in character literal" { + expect('💩' == 128169); +} + +test "result location zero sized array inside struct field implicit cast to slice" { + const E = struct { + entries: []u32, + }; + var foo = E{ .entries = &[_]u32{} }; + expect(foo.entries.len == 0); +} + +var global_foo: *i32 = undefined; + +test "global variable assignment with optional unwrapping with var initialized to undefined" { + const S = struct { + var data: i32 = 1234; + fn foo() ?*i32 { + return &data; + } + }; + global_foo = S.foo() orelse { + @panic("bad"); + }; + expect(global_foo.* == 1234); +} + +test "peer result location with typed parent, runtime condition, comptime prongs" { + const S = struct { + fn doTheTest(arg: i32) i32 { + const st = Structy{ + .bleh = if (arg == 1) 1 else 1, + }; + + if (st.bleh == 1) + return 1234; + return 0; + } + + const Structy = struct { + bleh: i32, + }; + }; + expect(S.doTheTest(0) == 1234); + expect(S.doTheTest(1) == 1234); +} + +test "nested optional field in struct" { + const S2 = struct { + y: u8, + }; + const S1 = struct { + x: ?S2, + }; + var s = S1{ + .x = S2{ .y = 127 }, + }; + expect(s.x.?.y == 127); +} + +fn maybe(x: bool) anyerror!?u32 { + return switch (x) { + true => @as(u32, 42), + else => null, + }; +} + +test "result location is optional inside error union" { + const x = maybe(true) catch unreachable; + expect(x.? == 42); +} + +threadlocal var buffer: [11]u8 = undefined; + +test "pointer to thread local array" { + const s = "Hello world"; + std.mem.copy(u8, buffer[0..], s); + std.testing.expectEqualSlices(u8, buffer[0..], s); +} + +test "auto created variables have correct alignment" { + const S = struct { + fn foo(str: [*]const u8) u32 { + for (@ptrCast([*]align(1) const u32, str)[0..1]) |v| { + return v; + } + return 0; + } + }; + expect(S.foo("\x7a\x7a\x7a\x7a") == 0x7a7a7a7a); + comptime expect(S.foo("\x7a\x7a\x7a\x7a") == 0x7a7a7a7a); +} + +extern var opaque_extern_var: opaque {}; +var var_to_export: u32 = 42; +test "extern variable with non-pointer opaque type" { + @export(var_to_export, .{ .name = "opaque_extern_var" }); + expect(@ptrCast(*align(1) u32, &opaque_extern_var).* == 42); +} + +test "lazy typeInfo value as generic parameter" { + const S = struct { + fn foo(args: anytype) void {} + }; + S.foo(@typeInfo(@TypeOf(.{}))); +} diff --git a/test/behavior/muladd.zig b/test/behavior/muladd.zig new file mode 100644 index 0000000000..d507f503f5 --- /dev/null +++ b/test/behavior/muladd.zig @@ -0,0 +1,34 @@ +const expect = @import("std").testing.expect; + +test "@mulAdd" { + comptime testMulAdd(); + testMulAdd(); +} + +fn testMulAdd() void { + { + var a: f16 = 5.5; + var b: f16 = 2.5; + var c: f16 = 6.25; + expect(@mulAdd(f16, a, b, c) == 20); + } + { + var a: f32 = 5.5; + var b: f32 = 2.5; + var c: f32 = 6.25; + expect(@mulAdd(f32, a, b, c) == 20); + } + { + var a: f64 = 5.5; + var b: f64 = 2.5; + var c: f64 = 6.25; + expect(@mulAdd(f64, a, b, c) == 20); + } + // Awaits implementation in libm.zig + //{ + // var a: f16 = 5.5; + // var b: f128 = 2.5; + // var c: f128 = 6.25; + // expect(@mulAdd(f128, a, b, c) == 20); + //} +} diff --git a/test/behavior/namespace_depends_on_compile_var.zig b/test/behavior/namespace_depends_on_compile_var.zig new file mode 100644 index 0000000000..8c5c19d733 --- /dev/null +++ b/test/behavior/namespace_depends_on_compile_var.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "namespace depends on compile var" { + if (some_namespace.a_bool) { + expect(some_namespace.a_bool); + } else { + expect(!some_namespace.a_bool); + } +} +const some_namespace = switch (std.builtin.os.tag) { + .linux => @import("namespace_depends_on_compile_var/a.zig"), + else => @import("namespace_depends_on_compile_var/b.zig"), +}; diff --git a/test/behavior/namespace_depends_on_compile_var/a.zig b/test/behavior/namespace_depends_on_compile_var/a.zig new file mode 100644 index 0000000000..5ce0e94f8b --- /dev/null +++ b/test/behavior/namespace_depends_on_compile_var/a.zig @@ -0,0 +1 @@ +pub const a_bool = true; diff --git a/test/behavior/namespace_depends_on_compile_var/b.zig b/test/behavior/namespace_depends_on_compile_var/b.zig new file mode 100644 index 0000000000..a12a54b589 --- /dev/null +++ b/test/behavior/namespace_depends_on_compile_var/b.zig @@ -0,0 +1 @@ +pub const a_bool = false; diff --git a/test/behavior/null.zig b/test/behavior/null.zig new file mode 100644 index 0000000000..8c9b86b260 --- /dev/null +++ b/test/behavior/null.zig @@ -0,0 +1,162 @@ +const expect = @import("std").testing.expect; + +test "optional type" { + const x: ?bool = true; + + if (x) |y| { + if (y) { + // OK + } else { + unreachable; + } + } else { + unreachable; + } + + const next_x: ?i32 = null; + + const z = next_x orelse 1234; + + expect(z == 1234); + + const final_x: ?i32 = 13; + + const num = final_x orelse unreachable; + + expect(num == 13); +} + +test "test maybe object and get a pointer to the inner value" { + var maybe_bool: ?bool = true; + + if (maybe_bool) |*b| { + b.* = false; + } + + expect(maybe_bool.? == false); +} + +test "rhs maybe unwrap return" { + const x: ?bool = true; + const y = x orelse return; +} + +test "maybe return" { + maybeReturnImpl(); + comptime maybeReturnImpl(); +} + +fn maybeReturnImpl() void { + expect(foo(1235).?); + if (foo(null) != null) unreachable; + expect(!foo(1234).?); +} + +fn foo(x: ?i32) ?bool { + const value = x orelse return null; + return value > 1234; +} + +test "if var maybe pointer" { + expect(shouldBeAPlus1(Particle{ + .a = 14, + .b = 1, + .c = 1, + .d = 1, + }) == 15); +} +fn shouldBeAPlus1(p: Particle) u64 { + var maybe_particle: ?Particle = p; + if (maybe_particle) |*particle| { + particle.a += 1; + } + if (maybe_particle) |particle| { + return particle.a; + } + return 0; +} +const Particle = struct { + a: u64, + b: u64, + c: u64, + d: u64, +}; + +test "null literal outside function" { + const is_null = here_is_a_null_literal.context == null; + expect(is_null); + + const is_non_null = here_is_a_null_literal.context != null; + expect(!is_non_null); +} +const SillyStruct = struct { + context: ?i32, +}; +const here_is_a_null_literal = SillyStruct{ .context = null }; + +test "test null runtime" { + testTestNullRuntime(null); +} +fn testTestNullRuntime(x: ?i32) void { + expect(x == null); + expect(!(x != null)); +} + +test "optional void" { + optionalVoidImpl(); + comptime optionalVoidImpl(); +} + +fn optionalVoidImpl() void { + expect(bar(null) == null); + expect(bar({}) != null); +} + +fn bar(x: ?void) ?void { + if (x) |_| { + return {}; + } else { + return null; + } +} + +const StructWithOptional = struct { + field: ?i32, +}; + +var struct_with_optional: StructWithOptional = undefined; + +test "unwrap optional which is field of global var" { + struct_with_optional.field = null; + if (struct_with_optional.field) |payload| { + unreachable; + } + struct_with_optional.field = 1234; + if (struct_with_optional.field) |payload| { + expect(payload == 1234); + } else { + unreachable; + } +} + +test "null with default unwrap" { + const x: i32 = null orelse 1; + expect(x == 1); +} + +test "optional types" { + comptime { + const opt_type_struct = StructWithOptionalType{ .t = u8 }; + expect(opt_type_struct.t != null and opt_type_struct.t.? == u8); + } +} + +const StructWithOptionalType = struct { + t: ?type, +}; + +test "optional pointer to 0 bit type null value at runtime" { + const EmptyStruct = struct {}; + var x: ?*EmptyStruct = null; + expect(x == null); +} diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig new file mode 100644 index 0000000000..1dc33eb8ea --- /dev/null +++ b/test/behavior/optional.zig @@ -0,0 +1,269 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +pub const EmptyStruct = struct {}; + +test "optional pointer to size zero struct" { + var e = EmptyStruct{}; + var o: ?*EmptyStruct = &e; + expect(o != null); +} + +test "equality compare nullable pointers" { + testNullPtrsEql(); + comptime testNullPtrsEql(); +} + +fn testNullPtrsEql() void { + var number: i32 = 1234; + + var x: ?*i32 = null; + var y: ?*i32 = null; + expect(x == y); + y = &number; + expect(x != y); + expect(x != &number); + expect(&number != x); + x = &number; + expect(x == y); + expect(x == &number); + expect(&number == x); +} + +test "address of unwrap optional" { + const S = struct { + const Foo = struct { + a: i32, + }; + + var global: ?Foo = null; + + pub fn getFoo() anyerror!*Foo { + return &global.?; + } + }; + S.global = S.Foo{ .a = 1234 }; + const foo = S.getFoo() catch unreachable; + expect(foo.a == 1234); +} + +test "equality compare optional with non-optional" { + test_cmp_optional_non_optional(); + comptime test_cmp_optional_non_optional(); +} + +fn test_cmp_optional_non_optional() void { + var ten: i32 = 10; + var opt_ten: ?i32 = 10; + var five: i32 = 5; + var int_n: ?i32 = null; + + expect(int_n != ten); + expect(opt_ten == ten); + expect(opt_ten != five); + + // test evaluation is always lexical + // ensure that the optional isn't always computed before the non-optional + var mutable_state: i32 = 0; + _ = blk1: { + mutable_state += 1; + break :blk1 @as(?f64, 10.0); + } != blk2: { + expect(mutable_state == 1); + break :blk2 @as(f64, 5.0); + }; + _ = blk1: { + mutable_state += 1; + break :blk1 @as(f64, 10.0); + } != blk2: { + expect(mutable_state == 2); + break :blk2 @as(?f64, 5.0); + }; +} + +test "passing an optional integer as a parameter" { + const S = struct { + fn entry() bool { + var x: i32 = 1234; + return foo(x); + } + + fn foo(x: ?i32) bool { + return x.? == 1234; + } + }; + expect(S.entry()); + comptime expect(S.entry()); +} + +test "unwrap function call with optional pointer return value" { + const S = struct { + fn entry() void { + expect(foo().?.* == 1234); + expect(bar() == null); + } + const global: i32 = 1234; + fn foo() ?*const i32 { + return &global; + } + fn bar() ?*i32 { + return null; + } + }; + S.entry(); + comptime S.entry(); +} + +test "nested orelse" { + const S = struct { + fn entry() void { + expect(func() == null); + } + fn maybe() ?Foo { + return null; + } + fn func() ?Foo { + const x = maybe() orelse + maybe() orelse + return null; + unreachable; + } + const Foo = struct { + field: i32, + }; + }; + S.entry(); + comptime S.entry(); +} + +test "self-referential struct through a slice of optional" { + const S = struct { + const Node = struct { + children: []?Node, + data: ?u8, + + fn new() Node { + return Node{ + .children = undefined, + .data = null, + }; + } + }; + }; + + var n = S.Node.new(); + expect(n.data == null); +} + +test "assigning to an unwrapped optional field in an inline loop" { + comptime var maybe_pos_arg: ?comptime_int = null; + inline for ("ab") |x| { + maybe_pos_arg = 0; + if (maybe_pos_arg.? != 0) { + @compileError("bad"); + } + maybe_pos_arg.? = 10; + } +} + +test "coerce an anon struct literal to optional struct" { + const S = struct { + const Struct = struct { + field: u32, + }; + export fn doTheTest() void { + var maybe_dims: ?Struct = null; + maybe_dims = .{ .field = 1 }; + expect(maybe_dims.?.field == 1); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "optional with void type" { + const Foo = struct { + x: ?void, + }; + var x = Foo{ .x = null }; + expect(x.x == null); +} + +test "0-bit child type coerced to optional return ptr result location" { + const S = struct { + fn doTheTest() void { + var y = Foo{}; + var z = y.thing(); + expect(z != null); + } + + const Foo = struct { + pub const Bar = struct { + field: *Foo, + }; + + pub fn thing(self: *Foo) ?Bar { + return Bar{ .field = self }; + } + }; + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "0-bit child type coerced to optional" { + const S = struct { + fn doTheTest() void { + var it: Foo = .{ + .list = undefined, + }; + expect(it.foo() != null); + } + + const Empty = struct {}; + const Foo = struct { + list: [10]Empty, + + fn foo(self: *Foo) ?*Empty { + const data = &self.list[0]; + return data; + } + }; + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "array of optional unaligned types" { + const Enum = enum { one, two, three }; + + const SomeUnion = union(enum) { + Num: Enum, + Other: u32, + }; + + const values = [_]?SomeUnion{ + SomeUnion{ .Num = .one }, + SomeUnion{ .Num = .two }, + SomeUnion{ .Num = .three }, + SomeUnion{ .Num = .one }, + SomeUnion{ .Num = .two }, + SomeUnion{ .Num = .three }, + }; + + // The index must be a runtime value + var i: usize = 0; + expectEqual(Enum.one, values[i].?.Num); + i += 1; + expectEqual(Enum.two, values[i].?.Num); + i += 1; + expectEqual(Enum.three, values[i].?.Num); + i += 1; + expectEqual(Enum.one, values[i].?.Num); + i += 1; + expectEqual(Enum.two, values[i].?.Num); + i += 1; + expectEqual(Enum.three, values[i].?.Num); +} diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig new file mode 100644 index 0000000000..a4f619b538 --- /dev/null +++ b/test/behavior/pointers.zig @@ -0,0 +1,339 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectError = testing.expectError; + +test "dereference pointer" { + comptime testDerefPtr(); + testDerefPtr(); +} + +fn testDerefPtr() void { + var x: i32 = 1234; + var y = &x; + y.* += 1; + expect(x == 1235); +} + +const Foo1 = struct { + x: void, +}; + +test "dereference pointer again" { + testDerefPtrOneVal(); + comptime testDerefPtrOneVal(); +} + +fn testDerefPtrOneVal() void { + // Foo1 satisfies the OnePossibleValueYes criteria + const x = &Foo1{ .x = {} }; + const y = x.*; + expect(@TypeOf(y.x) == void); +} + +test "pointer arithmetic" { + var ptr: [*]const u8 = "abcd"; + + expect(ptr[0] == 'a'); + ptr += 1; + expect(ptr[0] == 'b'); + ptr += 1; + expect(ptr[0] == 'c'); + ptr += 1; + expect(ptr[0] == 'd'); + ptr += 1; + expect(ptr[0] == 0); + ptr -= 1; + expect(ptr[0] == 'd'); + ptr -= 1; + expect(ptr[0] == 'c'); + ptr -= 1; + expect(ptr[0] == 'b'); + ptr -= 1; + expect(ptr[0] == 'a'); +} + +test "double pointer parsing" { + comptime expect(PtrOf(PtrOf(i32)) == **i32); +} + +fn PtrOf(comptime T: type) type { + return *T; +} + +test "assigning integer to C pointer" { + var x: i32 = 0; + var ptr: [*c]u8 = 0; + var ptr2: [*c]u8 = x; +} + +test "implicit cast single item pointer to C pointer and back" { + var y: u8 = 11; + var x: [*c]u8 = &y; + var z: *u8 = x; + z.* += 1; + expect(y == 12); +} + +test "C pointer comparison and arithmetic" { + const S = struct { + fn doTheTest() void { + var one: usize = 1; + var ptr1: [*c]u32 = 0; + var ptr2 = ptr1 + 10; + expect(ptr1 == 0); + expect(ptr1 >= 0); + expect(ptr1 <= 0); + // expect(ptr1 < 1); + // expect(ptr1 < one); + // expect(1 > ptr1); + // expect(one > ptr1); + expect(ptr1 < ptr2); + expect(ptr2 > ptr1); + expect(ptr2 >= 40); + expect(ptr2 == 40); + expect(ptr2 <= 40); + ptr2 -= 10; + expect(ptr1 == ptr2); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "peer type resolution with C pointers" { + var ptr_one: *u8 = undefined; + var ptr_many: [*]u8 = undefined; + var ptr_c: [*c]u8 = undefined; + var t = true; + var x1 = if (t) ptr_one else ptr_c; + var x2 = if (t) ptr_many else ptr_c; + var x3 = if (t) ptr_c else ptr_one; + var x4 = if (t) ptr_c else ptr_many; + expect(@TypeOf(x1) == [*c]u8); + expect(@TypeOf(x2) == [*c]u8); + expect(@TypeOf(x3) == [*c]u8); + expect(@TypeOf(x4) == [*c]u8); +} + +test "implicit casting between C pointer and optional non-C pointer" { + var slice: []const u8 = "aoeu"; + const opt_many_ptr: ?[*]const u8 = slice.ptr; + var ptr_opt_many_ptr = &opt_many_ptr; + var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr; + expect(c_ptr.*.* == 'a'); + ptr_opt_many_ptr = c_ptr; + expect(ptr_opt_many_ptr.*.?[1] == 'o'); +} + +test "implicit cast error unions with non-optional to optional pointer" { + const S = struct { + fn doTheTest() void { + expectError(error.Fail, foo()); + } + fn foo() anyerror!?*u8 { + return bar() orelse error.Fail; + } + fn bar() ?*u8 { + return null; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "initialize const optional C pointer to null" { + const a: ?[*c]i32 = null; + expect(a == null); + comptime expect(a == null); +} + +test "compare equality of optional and non-optional pointer" { + const a = @intToPtr(*const usize, 0x12345678); + const b = @intToPtr(?*usize, 0x12345678); + expect(a == b); + expect(b == a); +} + +test "allowzero pointer and slice" { + var ptr = @intToPtr([*]allowzero i32, 0); + var opt_ptr: ?[*]allowzero i32 = ptr; + expect(opt_ptr != null); + expect(@ptrToInt(ptr) == 0); + var runtime_zero: usize = 0; + var slice = ptr[runtime_zero..10]; + comptime expect(@TypeOf(slice) == []allowzero i32); + expect(@ptrToInt(&slice[5]) == 20); + + comptime expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero); + comptime expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero); +} + +test "assign null directly to C pointer and test null equality" { + var x: [*c]i32 = null; + expect(x == null); + expect(null == x); + expect(!(x != null)); + expect(!(null != x)); + if (x) |same_x| { + @panic("fail"); + } + var otherx: i32 = undefined; + expect((x orelse &otherx) == &otherx); + + const y: [*c]i32 = null; + comptime expect(y == null); + comptime expect(null == y); + comptime expect(!(y != null)); + comptime expect(!(null != y)); + if (y) |same_y| @panic("fail"); + const othery: i32 = undefined; + comptime expect((y orelse &othery) == &othery); + + var n: i32 = 1234; + var x1: [*c]i32 = &n; + expect(!(x1 == null)); + expect(!(null == x1)); + expect(x1 != null); + expect(null != x1); + expect(x1.?.* == 1234); + if (x1) |same_x1| { + expect(same_x1.* == 1234); + } else { + @panic("fail"); + } + expect((x1 orelse &otherx) == x1); + + const nc: i32 = 1234; + const y1: [*c]const i32 = &nc; + comptime expect(!(y1 == null)); + comptime expect(!(null == y1)); + comptime expect(y1 != null); + comptime expect(null != y1); + comptime expect(y1.?.* == 1234); + if (y1) |same_y1| { + expect(same_y1.* == 1234); + } else { + @compileError("fail"); + } + comptime expect((y1 orelse &othery) == y1); +} + +test "null terminated pointer" { + const S = struct { + fn doTheTest() void { + var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' }; + var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero); + var no_zero_ptr: [*]const u8 = zero_ptr; + var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr); + expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello")); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "allow any sentinel" { + const S = struct { + fn doTheTest() void { + var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 }; + var ptr: [*:std.math.minInt(i32)]i32 = &array; + expect(ptr[4] == std.math.minInt(i32)); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "pointer sentinel with enums" { + const S = struct { + const Number = enum { + one, + two, + sentinel, + }; + + fn doTheTest() void { + var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one }; + expect(ptr[4] == .sentinel); // TODO this should be comptime expect, see #3731 + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "pointer sentinel with optional element" { + const S = struct { + fn doTheTest() void { + var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 }; + expect(ptr[4] == null); // TODO this should be comptime expect, see #3731 + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "pointer sentinel with +inf" { + const S = struct { + fn doTheTest() void { + const inf = std.math.inf_f32; + var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 }; + expect(ptr[4] == inf); // TODO this should be comptime expect, see #3731 + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "pointer to array at fixed address" { + const array = @intToPtr(*volatile [1]u32, 0x10); + // Silly check just to reference `array` + expect(@ptrToInt(&array[0]) == 0x10); +} + +test "pointer arithmetic affects the alignment" { + { + var ptr: [*]align(8) u32 = undefined; + var x: usize = 1; + + expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8); + const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4 + expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4); + const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8 + expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8); + const ptr3 = ptr + 0; // no-op + expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + x; // runtime-known addend + expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } + { + var ptr: [*]align(8) [3]u8 = undefined; + var x: usize = 1; + + const ptr1 = ptr + 17; // 3 * 17 = 51 + expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1); + const ptr2 = ptr + x; // runtime-known addend + expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1); + const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8 + expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4 + expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } +} + +test "@ptrToInt on null optional at comptime" { + { + const pointer = @intToPtr(?*u8, 0x000); + const x = @ptrToInt(pointer); + comptime expect(0 == @ptrToInt(pointer)); + } + { + const pointer = @intToPtr(?*u8, 0xf00); + comptime expect(0xf00 == @ptrToInt(pointer)); + } +} + +test "indexing array with sentinel returns correct type" { + var s: [:0]const u8 = "abc"; + testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0]))); +} diff --git a/test/behavior/popcount.zig b/test/behavior/popcount.zig new file mode 100644 index 0000000000..884a7bdb6d --- /dev/null +++ b/test/behavior/popcount.zig @@ -0,0 +1,43 @@ +const expect = @import("std").testing.expect; + +test "@popCount" { + comptime testPopCount(); + testPopCount(); +} + +fn testPopCount() void { + { + var x: u32 = 0xffffffff; + expect(@popCount(u32, x) == 32); + } + { + var x: u5 = 0x1f; + expect(@popCount(u5, x) == 5); + } + { + var x: u32 = 0xaa; + expect(@popCount(u32, x) == 4); + } + { + var x: u32 = 0xaaaaaaaa; + expect(@popCount(u32, x) == 16); + } + { + var x: u32 = 0xaaaaaaaa; + expect(@popCount(u32, x) == 16); + } + { + var x: i16 = -1; + expect(@popCount(i16, x) == 16); + } + { + var x: i8 = -120; + expect(@popCount(i8, x) == 2); + } + comptime { + expect(@popCount(u8, @bitCast(u8, @as(i8, -120))) == 2); + } + comptime { + expect(@popCount(i128, 0b11111111000110001100010000100001000011000011100101010001) == 24); + } +} diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig new file mode 100644 index 0000000000..2f6ce1243f --- /dev/null +++ b/test/behavior/ptrcast.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const expect = std.testing.expect; +const native_endian = builtin.target.cpu.arch.endian(); + +test "reinterpret bytes as integer with nonzero offset" { + testReinterpretBytesAsInteger(); + comptime testReinterpretBytesAsInteger(); +} + +fn testReinterpretBytesAsInteger() void { + const bytes = "\x12\x34\x56\x78\xab"; + const expected = switch (native_endian) { + .Little => 0xab785634, + .Big => 0x345678ab, + }; + expect(@ptrCast(*align(1) const u32, bytes[1..5]).* == expected); +} + +test "reinterpret bytes of an array into an extern struct" { + testReinterpretBytesAsExternStruct(); + comptime testReinterpretBytesAsExternStruct(); +} + +fn testReinterpretBytesAsExternStruct() void { + var bytes align(2) = [_]u8{ 1, 2, 3, 4, 5, 6 }; + + const S = extern struct { + a: u8, + b: u16, + c: u8, + }; + + var ptr = @ptrCast(*const S, &bytes); + var val = ptr.c; + expect(val == 5); +} + +test "reinterpret struct field at comptime" { + const numNative = comptime Bytes.init(0x12345678); + if (native_endian != .Little) { + expect(std.mem.eql(u8, &[_]u8{ 0x12, 0x34, 0x56, 0x78 }, &numNative.bytes)); + } else { + expect(std.mem.eql(u8, &[_]u8{ 0x78, 0x56, 0x34, 0x12 }, &numNative.bytes)); + } +} + +const Bytes = struct { + bytes: [4]u8, + + pub fn init(v: u32) Bytes { + var res: Bytes = undefined; + @ptrCast(*align(1) u32, &res.bytes).* = v; + + return res; + } +}; + +test "comptime ptrcast keeps larger alignment" { + comptime { + const a: u32 = 1234; + const p = @ptrCast([*]const u8, &a); + std.debug.assert(@TypeOf(p) == [*]align(@alignOf(u32)) const u8); + } +} + +test "implicit optional pointer to optional c_void pointer" { + var buf: [4]u8 = "aoeu".*; + var x: ?[*]u8 = &buf; + var y: ?*c_void = x; + var z = @ptrCast(*[4]u8, y); + expect(std.mem.eql(u8, z, "aoeu")); +} diff --git a/test/behavior/pub_enum.zig b/test/behavior/pub_enum.zig new file mode 100644 index 0000000000..0613df94d9 --- /dev/null +++ b/test/behavior/pub_enum.zig @@ -0,0 +1,13 @@ +const other = @import("pub_enum/other.zig"); +const expect = @import("std").testing.expect; + +test "pub enum" { + pubEnumTest(other.APubEnum.Two); +} +fn pubEnumTest(foo: other.APubEnum) void { + expect(foo == other.APubEnum.Two); +} + +test "cast with imported symbol" { + expect(@as(other.size_t, 42) == 42); +} diff --git a/test/behavior/pub_enum/other.zig b/test/behavior/pub_enum/other.zig new file mode 100644 index 0000000000..c663950383 --- /dev/null +++ b/test/behavior/pub_enum/other.zig @@ -0,0 +1,6 @@ +pub const APubEnum = enum { + One, + Two, + Three, +}; +pub const size_t = u64; diff --git a/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig new file mode 100644 index 0000000000..2c1cf06268 --- /dev/null +++ b/test/behavior/ref_var_in_if_after_if_2nd_switch_prong.zig @@ -0,0 +1,37 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; + +var ok: bool = false; +test "reference a variable in an if after an if in the 2nd switch prong" { + foo(true, Num.Two, false, "aoeu"); + expect(!ok); + foo(false, Num.One, false, "aoeu"); + expect(!ok); + foo(true, Num.One, false, "aoeu"); + expect(ok); +} + +const Num = enum { + One, + Two, +}; + +fn foo(c: bool, k: Num, c2: bool, b: []const u8) void { + switch (k) { + Num.Two => {}, + Num.One => { + if (c) { + const output_path = b; + + if (c2) {} + + a(output_path); + } + }, + } +} + +fn a(x: []const u8) void { + expect(mem.eql(u8, x, "aoeu")); + ok = true; +} diff --git a/test/behavior/reflection.zig b/test/behavior/reflection.zig new file mode 100644 index 0000000000..6d1c341713 --- /dev/null +++ b/test/behavior/reflection.zig @@ -0,0 +1,55 @@ +const expect = @import("std").testing.expect; +const mem = @import("std").mem; +const reflection = @This(); + +test "reflection: function return type, var args, and param types" { + comptime { + const info = @typeInfo(@TypeOf(dummy)).Fn; + expect(info.return_type.? == i32); + expect(!info.is_var_args); + expect(info.args.len == 3); + expect(info.args[0].arg_type.? == bool); + expect(info.args[1].arg_type.? == i32); + expect(info.args[2].arg_type.? == f32); + } +} + +fn dummy(a: bool, b: i32, c: f32) i32 { + return 1234; +} + +test "reflection: @field" { + var f = Foo{ + .one = 42, + .two = true, + .three = void{}, + }; + + expect(f.one == f.one); + expect(@field(f, "o" ++ "ne") == f.one); + expect(@field(f, "t" ++ "wo") == f.two); + expect(@field(f, "th" ++ "ree") == f.three); + expect(@field(Foo, "const" ++ "ant") == Foo.constant); + expect(@field(Bar, "O" ++ "ne") == Bar.One); + expect(@field(Bar, "T" ++ "wo") == Bar.Two); + expect(@field(Bar, "Th" ++ "ree") == Bar.Three); + expect(@field(Bar, "F" ++ "our") == Bar.Four); + expect(@field(reflection, "dum" ++ "my")(true, 1, 2) == dummy(true, 1, 2)); + @field(f, "o" ++ "ne") = 4; + expect(f.one == 4); +} + +const Foo = struct { + const constant = 52; + + one: i32, + two: bool, + three: void, +}; + +const Bar = union(enum) { + One: void, + Two: i32, + Three: bool, + Four: f64, +}; diff --git a/test/behavior/shuffle.zig b/test/behavior/shuffle.zig new file mode 100644 index 0000000000..3c26adbd48 --- /dev/null +++ b/test/behavior/shuffle.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const mem = std.mem; +const expect = std.testing.expect; +const Vector = std.meta.Vector; + +test "@shuffle" { + // TODO investigate why this fails when cross-compiling to wasm. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; + var x: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 }; + const mask: Vector(4, i32) = [4]i32{ 0, ~@as(i32, 2), 3, ~@as(i32, 3) }; + var res = @shuffle(i32, v, x, mask); + expect(mem.eql(i32, &@as([4]i32, res), &[4]i32{ 2147483647, 3, 40, 4 })); + + // Implicit cast from array (of mask) + res = @shuffle(i32, v, x, [4]i32{ 0, ~@as(i32, 2), 3, ~@as(i32, 3) }); + expect(mem.eql(i32, &@as([4]i32, res), &[4]i32{ 2147483647, 3, 40, 4 })); + + // Undefined + const mask2: Vector(4, i32) = [4]i32{ 3, 1, 2, 0 }; + res = @shuffle(i32, v, undefined, mask2); + expect(mem.eql(i32, &@as([4]i32, res), &[4]i32{ 40, -2, 30, 2147483647 })); + + // Upcasting of b + var v2: Vector(2, i32) = [2]i32{ 2147483647, undefined }; + const mask3: Vector(4, i32) = [4]i32{ ~@as(i32, 0), 2, ~@as(i32, 0), 3 }; + res = @shuffle(i32, x, v2, mask3); + expect(mem.eql(i32, &@as([4]i32, res), &[4]i32{ 2147483647, 3, 2147483647, 4 })); + + // Upcasting of a + var v3: Vector(2, i32) = [2]i32{ 2147483647, -2 }; + const mask4: Vector(4, i32) = [4]i32{ 0, ~@as(i32, 2), 1, ~@as(i32, 3) }; + res = @shuffle(i32, v3, x, mask4); + expect(mem.eql(i32, &@as([4]i32, res), &[4]i32{ 2147483647, 3, -2, 4 })); + + // bool + // https://github.com/ziglang/zig/issues/3317 + if (builtin.target.cpu.arch != .mipsel and builtin.target.cpu.arch != .mips) { + var x2: Vector(4, bool) = [4]bool{ false, true, false, true }; + var v4: Vector(2, bool) = [2]bool{ true, false }; + const mask5: Vector(4, i32) = [4]i32{ 0, ~@as(i32, 1), 1, 2 }; + var res2 = @shuffle(bool, x2, v4, mask5); + expect(mem.eql(bool, &@as([4]bool, res2), &[4]bool{ false, false, true, false })); + } + + // TODO re-enable when LLVM codegen is fixed + // https://github.com/ziglang/zig/issues/3246 + if (false) { + var x2: Vector(3, bool) = [3]bool{ false, true, false }; + var v4: Vector(2, bool) = [2]bool{ true, false }; + const mask5: Vector(4, i32) = [4]i32{ 0, ~@as(i32, 1), 1, 2 }; + var res2 = @shuffle(bool, x2, v4, mask5); + expect(mem.eql(bool, &@as([4]bool, res2), &[4]bool{ false, false, true, false })); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig new file mode 100644 index 0000000000..54efe9cb35 --- /dev/null +++ b/test/behavior/sizeof_and_typeof.zig @@ -0,0 +1,264 @@ +const std = @import("std"); +const builtin = std.builtin; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +test "@sizeOf and @TypeOf" { + const y: @TypeOf(x) = 120; + expect(@sizeOf(@TypeOf(y)) == 2); +} +const x: u16 = 13; +const z: @TypeOf(x) = 19; + +const A = struct { + a: u8, + b: u32, + c: u8, + d: u3, + e: u5, + f: u16, + g: u16, + h: u9, + i: u7, +}; + +const P = packed struct { + a: u8, + b: u32, + c: u8, + d: u3, + e: u5, + f: u16, + g: u16, + h: u9, + i: u7, +}; + +test "@byteOffsetOf" { + // Packed structs have fixed memory layout + expect(@byteOffsetOf(P, "a") == 0); + expect(@byteOffsetOf(P, "b") == 1); + expect(@byteOffsetOf(P, "c") == 5); + expect(@byteOffsetOf(P, "d") == 6); + expect(@byteOffsetOf(P, "e") == 6); + expect(@byteOffsetOf(P, "f") == 7); + expect(@byteOffsetOf(P, "g") == 9); + expect(@byteOffsetOf(P, "h") == 11); + expect(@byteOffsetOf(P, "i") == 12); + + // Normal struct fields can be moved/padded + var a: A = undefined; + expect(@ptrToInt(&a.a) - @ptrToInt(&a) == @byteOffsetOf(A, "a")); + expect(@ptrToInt(&a.b) - @ptrToInt(&a) == @byteOffsetOf(A, "b")); + expect(@ptrToInt(&a.c) - @ptrToInt(&a) == @byteOffsetOf(A, "c")); + expect(@ptrToInt(&a.d) - @ptrToInt(&a) == @byteOffsetOf(A, "d")); + expect(@ptrToInt(&a.e) - @ptrToInt(&a) == @byteOffsetOf(A, "e")); + expect(@ptrToInt(&a.f) - @ptrToInt(&a) == @byteOffsetOf(A, "f")); + expect(@ptrToInt(&a.g) - @ptrToInt(&a) == @byteOffsetOf(A, "g")); + expect(@ptrToInt(&a.h) - @ptrToInt(&a) == @byteOffsetOf(A, "h")); + expect(@ptrToInt(&a.i) - @ptrToInt(&a) == @byteOffsetOf(A, "i")); +} + +test "@byteOffsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" { + const p3a_len = 3; + const P3 = packed struct { + a: [p3a_len]u8, + b: usize, + }; + std.testing.expectEqual(0, @byteOffsetOf(P3, "a")); + std.testing.expectEqual(p3a_len, @byteOffsetOf(P3, "b")); + + const p5a_len = 5; + const P5 = packed struct { + a: [p5a_len]u8, + b: usize, + }; + std.testing.expectEqual(0, @byteOffsetOf(P5, "a")); + std.testing.expectEqual(p5a_len, @byteOffsetOf(P5, "b")); + + const p6a_len = 6; + const P6 = packed struct { + a: [p6a_len]u8, + b: usize, + }; + std.testing.expectEqual(0, @byteOffsetOf(P6, "a")); + std.testing.expectEqual(p6a_len, @byteOffsetOf(P6, "b")); + + const p7a_len = 7; + const P7 = packed struct { + a: [p7a_len]u8, + b: usize, + }; + std.testing.expectEqual(0, @byteOffsetOf(P7, "a")); + std.testing.expectEqual(p7a_len, @byteOffsetOf(P7, "b")); + + const p9a_len = 9; + const P9 = packed struct { + a: [p9a_len]u8, + b: usize, + }; + std.testing.expectEqual(0, @byteOffsetOf(P9, "a")); + std.testing.expectEqual(p9a_len, @byteOffsetOf(P9, "b")); + + // 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25 etc. are further cases +} + +test "@bitOffsetOf" { + // Packed structs have fixed memory layout + expect(@bitOffsetOf(P, "a") == 0); + expect(@bitOffsetOf(P, "b") == 8); + expect(@bitOffsetOf(P, "c") == 40); + expect(@bitOffsetOf(P, "d") == 48); + expect(@bitOffsetOf(P, "e") == 51); + expect(@bitOffsetOf(P, "f") == 56); + expect(@bitOffsetOf(P, "g") == 72); + + expect(@byteOffsetOf(A, "a") * 8 == @bitOffsetOf(A, "a")); + expect(@byteOffsetOf(A, "b") * 8 == @bitOffsetOf(A, "b")); + expect(@byteOffsetOf(A, "c") * 8 == @bitOffsetOf(A, "c")); + expect(@byteOffsetOf(A, "d") * 8 == @bitOffsetOf(A, "d")); + expect(@byteOffsetOf(A, "e") * 8 == @bitOffsetOf(A, "e")); + expect(@byteOffsetOf(A, "f") * 8 == @bitOffsetOf(A, "f")); + expect(@byteOffsetOf(A, "g") * 8 == @bitOffsetOf(A, "g")); +} + +test "@sizeOf on compile-time types" { + expect(@sizeOf(comptime_int) == 0); + expect(@sizeOf(comptime_float) == 0); + expect(@sizeOf(@TypeOf(.hi)) == 0); + expect(@sizeOf(@TypeOf(type)) == 0); +} + +test "@sizeOf(T) == 0 doesn't force resolving struct size" { + const S = struct { + const Foo = struct { + y: if (@sizeOf(Foo) == 0) u64 else u32, + }; + const Bar = struct { + x: i32, + y: if (0 == @sizeOf(Bar)) u64 else u32, + }; + }; + + expect(@sizeOf(S.Foo) == 4); + expect(@sizeOf(S.Bar) == 8); +} + +test "@TypeOf() has no runtime side effects" { + const S = struct { + fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; + } + }; + var data: i32 = 0; + const T = @TypeOf(S.foo(i32, &data)); + comptime expect(T == i32); + expect(data == 0); +} + +test "@TypeOf() with multiple arguments" { + { + var var_1: u32 = undefined; + var var_2: u8 = undefined; + var var_3: u64 = undefined; + comptime expect(@TypeOf(var_1, var_2, var_3) == u64); + } + { + var var_1: f16 = undefined; + var var_2: f32 = undefined; + var var_3: f64 = undefined; + comptime expect(@TypeOf(var_1, var_2, var_3) == f64); + } + { + var var_1: u16 = undefined; + comptime expect(@TypeOf(var_1, 0xffff) == u16); + } + { + var var_1: f32 = undefined; + comptime expect(@TypeOf(var_1, 3.1415) == f32); + } +} + +test "branching logic inside @TypeOf" { + const S = struct { + var data: i32 = 0; + fn foo() anyerror!i32 { + data += 1; + return undefined; + } + }; + const T = @TypeOf(S.foo() catch undefined); + comptime expect(T == i32); + expect(S.data == 0); +} + +fn fn1(alpha: bool) void { + const n: usize = 7; + const v = if (alpha) n else @sizeOf(usize); +} + +test "lazy @sizeOf result is checked for definedness" { + const f = fn1; +} + +test "@bitSizeOf" { + expect(@bitSizeOf(u2) == 2); + expect(@bitSizeOf(u8) == @sizeOf(u8) * 8); + expect(@bitSizeOf(struct { + a: u2, + }) == 8); + expect(@bitSizeOf(packed struct { + a: u2, + }) == 2); +} + +test "@sizeOf comparison against zero" { + const S0 = struct { + f: *@This(), + }; + const U0 = union { + f: *@This(), + }; + const S1 = struct { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const U1 = union { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const S = struct { + fn doTheTest(comptime T: type, comptime result: bool) void { + expectEqual(result, @sizeOf(T) > 0); + } + }; + // Zero-sized type + S.doTheTest(u0, false); + S.doTheTest(*u0, false); + // Non byte-sized type + S.doTheTest(u1, true); + S.doTheTest(*u1, true); + // Regular type + S.doTheTest(u8, true); + S.doTheTest(*u8, true); + S.doTheTest(f32, true); + S.doTheTest(*f32, true); + // Container with ptr pointing to themselves + S.doTheTest(S0, true); + S.doTheTest(U0, true); + S.doTheTest(S1, true); + S.doTheTest(U1, true); +} diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig new file mode 100644 index 0000000000..330e218f93 --- /dev/null +++ b/test/behavior/slice.zig @@ -0,0 +1,337 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqualSlices = std.testing.expectEqualSlices; +const expectEqual = std.testing.expectEqual; +const mem = std.mem; + +const x = @intToPtr([*]i32, 0x1000)[0..0x500]; +const y = x[0x100..]; +test "compile time slice of pointer to hard coded address" { + expect(@ptrToInt(x) == 0x1000); + expect(x.len == 0x500); + + expect(@ptrToInt(y) == 0x1100); + expect(y.len == 0x400); +} + +test "runtime safety lets us slice from len..len" { + var an_array = [_]u8{ + 1, + 2, + 3, + }; + expect(mem.eql(u8, sliceFromLenToLen(an_array[0..], 3, 3), "")); +} + +fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 { + return a_slice[start..end]; +} + +test "implicitly cast array of size 0 to slice" { + var msg = [_]u8{}; + assertLenIsZero(&msg); +} + +fn assertLenIsZero(msg: []const u8) void { + expect(msg.len == 0); +} + +test "C pointer" { + var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf"; + var len: u32 = 10; + var slice = buf[0..len]; + expectEqualSlices(u8, "kjdhfkjdhf", slice); +} + +test "C pointer slice access" { + var buf: [10]u32 = [1]u32{42} ** 10; + const c_ptr = @ptrCast([*c]const u32, &buf); + + var runtime_zero: usize = 0; + comptime expectEqual([]const u32, @TypeOf(c_ptr[runtime_zero..1])); + comptime expectEqual(*const [1]u32, @TypeOf(c_ptr[0..1])); + + for (c_ptr[0..5]) |*cl| { + expectEqual(@as(u32, 42), cl.*); + } +} + +fn sliceSum(comptime q: []const u8) i32 { + comptime var result = 0; + inline for (q) |item| { + result += item; + } + return result; +} + +test "comptime slices are disambiguated" { + expect(sliceSum(&[_]u8{ 1, 2 }) == 3); + expect(sliceSum(&[_]u8{ 3, 4 }) == 7); +} + +test "slice type with custom alignment" { + const LazilyResolvedType = struct { + anything: i32, + }; + var slice: []align(32) LazilyResolvedType = undefined; + var array: [10]LazilyResolvedType align(32) = undefined; + slice = &array; + slice[1].anything = 42; + expect(array[1].anything == 42); +} + +test "access len index of sentinel-terminated slice" { + const S = struct { + fn doTheTest() void { + var slice: [:0]const u8 = "hello"; + + expect(slice.len == 5); + expect(slice[5] == 0); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "obtaining a null terminated slice" { + // here we have a normal array + var buf: [50]u8 = undefined; + + buf[0] = 'a'; + buf[1] = 'b'; + buf[2] = 'c'; + buf[3] = 0; + + // now we obtain a null terminated slice: + const ptr = buf[0..3 :0]; + + var runtime_len: usize = 3; + const ptr2 = buf[0..runtime_len :0]; + // ptr2 is a null-terminated slice + comptime expect(@TypeOf(ptr2) == [:0]u8); + comptime expect(@TypeOf(ptr2[0..2]) == *[2]u8); + var runtime_zero: usize = 0; + comptime expect(@TypeOf(ptr2[runtime_zero..2]) == []u8); +} + +test "empty array to slice" { + const S = struct { + fn doTheTest() void { + const empty: []align(16) u8 = &[_]u8{}; + const align_1: []align(1) u8 = empty; + const align_4: []align(4) u8 = empty; + const align_16: []align(16) u8 = empty; + expectEqual(1, @typeInfo(@TypeOf(align_1)).Pointer.alignment); + expectEqual(4, @typeInfo(@TypeOf(align_4)).Pointer.alignment); + expectEqual(16, @typeInfo(@TypeOf(align_16)).Pointer.alignment); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "@ptrCast slice to pointer" { + const S = struct { + fn doTheTest() void { + var array align(@alignOf(u16)) = [5]u8{ 0xff, 0xff, 0xff, 0xff, 0xff }; + var slice: []u8 = &array; + var ptr = @ptrCast(*u16, slice); + expect(ptr.* == 65535); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "slice syntax resulting in pointer-to-array" { + const S = struct { + fn doTheTest() void { + testArray(); + testArrayZ(); + testArray0(); + testArrayAlign(); + testPointer(); + testPointerZ(); + testPointer0(); + testPointerAlign(); + testSlice(); + testSliceZ(); + testSlice0(); + testSliceOpt(); + testSliceAlign(); + } + + fn testArray() void { + var array = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = array[1..3]; + comptime expect(@TypeOf(slice) == *[2]u8); + expect(slice[0] == 2); + expect(slice[1] == 3); + } + + fn testArrayZ() void { + var array = [5:0]u8{ 1, 2, 3, 4, 5 }; + comptime expect(@TypeOf(array[1..3]) == *[2]u8); + comptime expect(@TypeOf(array[1..5]) == *[4:0]u8); + comptime expect(@TypeOf(array[1..]) == *[4:0]u8); + comptime expect(@TypeOf(array[1..3 :4]) == *[2:4]u8); + } + + fn testArray0() void { + { + var array = [0]u8{}; + var slice = array[0..0]; + comptime expect(@TypeOf(slice) == *[0]u8); + } + { + var array = [0:0]u8{}; + var slice = array[0..0]; + comptime expect(@TypeOf(slice) == *[0:0]u8); + expect(slice[0] == 0); + } + } + + fn testArrayAlign() void { + var array align(4) = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = array[4..5]; + comptime expect(@TypeOf(slice) == *align(4) [1]u8); + expect(slice[0] == 5); + comptime expect(@TypeOf(array[0..2]) == *align(4) [2]u8); + } + + fn testPointer() void { + var array = [5]u8{ 1, 2, 3, 4, 5 }; + var pointer: [*]u8 = &array; + var slice = pointer[1..3]; + comptime expect(@TypeOf(slice) == *[2]u8); + expect(slice[0] == 2); + expect(slice[1] == 3); + } + + fn testPointerZ() void { + var array = [5:0]u8{ 1, 2, 3, 4, 5 }; + var pointer: [*:0]u8 = &array; + comptime expect(@TypeOf(pointer[1..3]) == *[2]u8); + comptime expect(@TypeOf(pointer[1..3 :4]) == *[2:4]u8); + } + + fn testPointer0() void { + var pointer: [*]const u0 = &[1]u0{0}; + var slice = pointer[0..1]; + comptime expect(@TypeOf(slice) == *const [1]u0); + expect(slice[0] == 0); + } + + fn testPointerAlign() void { + var array align(4) = [5]u8{ 1, 2, 3, 4, 5 }; + var pointer: [*]align(4) u8 = &array; + var slice = pointer[4..5]; + comptime expect(@TypeOf(slice) == *align(4) [1]u8); + expect(slice[0] == 5); + comptime expect(@TypeOf(pointer[0..2]) == *align(4) [2]u8); + } + + fn testSlice() void { + var array = [5]u8{ 1, 2, 3, 4, 5 }; + var src_slice: []u8 = &array; + var slice = src_slice[1..3]; + comptime expect(@TypeOf(slice) == *[2]u8); + expect(slice[0] == 2); + expect(slice[1] == 3); + } + + fn testSliceZ() void { + var array = [5:0]u8{ 1, 2, 3, 4, 5 }; + var slice: [:0]u8 = &array; + comptime expect(@TypeOf(slice[1..3]) == *[2]u8); + comptime expect(@TypeOf(slice[1..]) == [:0]u8); + comptime expect(@TypeOf(slice[1..3 :4]) == *[2:4]u8); + } + + fn testSliceOpt() void { + var array: [2]u8 = [2]u8{ 1, 2 }; + var slice: ?[]u8 = &array; + comptime expect(@TypeOf(&array, slice) == ?[]u8); + comptime expect(@TypeOf(slice.?[0..2]) == *[2]u8); + } + + fn testSlice0() void { + { + var array = [0]u8{}; + var src_slice: []u8 = &array; + var slice = src_slice[0..0]; + comptime expect(@TypeOf(slice) == *[0]u8); + } + { + var array = [0:0]u8{}; + var src_slice: [:0]u8 = &array; + var slice = src_slice[0..0]; + comptime expect(@TypeOf(slice) == *[0]u8); + } + } + + fn testSliceAlign() void { + var array align(4) = [5]u8{ 1, 2, 3, 4, 5 }; + var src_slice: []align(4) u8 = &array; + var slice = src_slice[4..5]; + comptime expect(@TypeOf(slice) == *align(4) [1]u8); + expect(slice[0] == 5); + comptime expect(@TypeOf(src_slice[0..2]) == *align(4) [2]u8); + } + + fn testConcatStrLiterals() void { + expectEqualSlices("a"[0..] ++ "b"[0..], "ab"); + expectEqualSlices("a"[0..:0] ++ "b"[0..:0], "ab"); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "slice of hardcoded address to pointer" { + const S = struct { + fn doTheTest() void { + const pointer = @intToPtr([*]u8, 0x04)[0..2]; + comptime expect(@TypeOf(pointer) == *[2]u8); + const slice: []const u8 = pointer; + expect(@ptrToInt(slice.ptr) == 4); + expect(slice.len == 2); + } + }; + + S.doTheTest(); +} + +test "type coercion of pointer to anon struct literal to pointer to slice" { + const S = struct { + const U = union{ + a: u32, + b: bool, + c: []const u8, + }; + + fn doTheTest() void { + var x1: u8 = 42; + const t1 = &.{ x1, 56, 54 }; + var slice1: []const u8 = t1; + expect(slice1.len == 3); + expect(slice1[0] == 42); + expect(slice1[1] == 56); + expect(slice1[2] == 54); + + var x2: []const u8 = "hello"; + const t2 = &.{ x2, ", ", "world!" }; + // @compileLog(@TypeOf(t2)); + var slice2: []const []const u8 = t2; + expect(slice2.len == 3); + expect(mem.eql(u8, slice2[0], "hello")); + expect(mem.eql(u8, slice2[1], ", ")); + expect(mem.eql(u8, slice2[2], "world!")); + } + }; + // S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/slice_sentinel_comptime.zig b/test/behavior/slice_sentinel_comptime.zig new file mode 100644 index 0000000000..79da5d3c52 --- /dev/null +++ b/test/behavior/slice_sentinel_comptime.zig @@ -0,0 +1,199 @@ +test "comptime slice-sentinel in bounds (unterminated)" { + // array + comptime { + var target = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + const slice = target[0..3 :'d']; + } + + // ptr_array + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target = &buf; + const slice = target[0..3 :'d']; + } + + // vector_ConstPtrSpecialBaseArray + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = &buf; + const slice = target[0..3 :'d']; + } + + // vector_ConstPtrSpecialRef + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = @ptrCast([*]u8, &buf); + const slice = target[0..3 :'d']; + } + + // cvector_ConstPtrSpecialBaseArray + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = &buf; + const slice = target[0..3 :'d']; + } + + // cvector_ConstPtrSpecialRef + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = @ptrCast([*c]u8, &buf); + const slice = target[0..3 :'d']; + } + + // slice + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: []u8 = &buf; + const slice = target[0..3 :'d']; + } +} + +test "comptime slice-sentinel in bounds (end,unterminated)" { + // array + comptime { + var target = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + const slice = target[0..13 :0xff]; + } + + // ptr_array + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target = &buf; + const slice = target[0..13 :0xff]; + } + + // vector_ConstPtrSpecialBaseArray + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target: [*]u8 = &buf; + const slice = target[0..13 :0xff]; + } + + // vector_ConstPtrSpecialRef + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target: [*]u8 = @ptrCast([*]u8, &buf); + const slice = target[0..13 :0xff]; + } + + // cvector_ConstPtrSpecialBaseArray + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target: [*c]u8 = &buf; + const slice = target[0..13 :0xff]; + } + + // cvector_ConstPtrSpecialRef + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target: [*c]u8 = @ptrCast([*c]u8, &buf); + const slice = target[0..13 :0xff]; + } + + // slice + comptime { + var buf = [_]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{0xff} ** 10; + var target: []u8 = &buf; + const slice = target[0..13 :0xff]; + } +} + +test "comptime slice-sentinel in bounds (terminated)" { + // array + comptime { + var target = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + const slice = target[0..3 :'d']; + } + + // ptr_array + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target = &buf; + const slice = target[0..3 :'d']; + } + + // vector_ConstPtrSpecialBaseArray + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = &buf; + const slice = target[0..3 :'d']; + } + + // vector_ConstPtrSpecialRef + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = @ptrCast([*]u8, &buf); + const slice = target[0..3 :'d']; + } + + // cvector_ConstPtrSpecialBaseArray + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = &buf; + const slice = target[0..3 :'d']; + } + + // cvector_ConstPtrSpecialRef + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = @ptrCast([*c]u8, &buf); + const slice = target[0..3 :'d']; + } + + // slice + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: []u8 = &buf; + const slice = target[0..3 :'d']; + } +} + +test "comptime slice-sentinel in bounds (on target sentinel)" { + // array + comptime { + var target = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + const slice = target[0..14 :0]; + } + + // ptr_array + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target = &buf; + const slice = target[0..14 :0]; + } + + // vector_ConstPtrSpecialBaseArray + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = &buf; + const slice = target[0..14 :0]; + } + + // vector_ConstPtrSpecialRef + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*]u8 = @ptrCast([*]u8, &buf); + const slice = target[0..14 :0]; + } + + // cvector_ConstPtrSpecialBaseArray + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = &buf; + const slice = target[0..14 :0]; + } + + // cvector_ConstPtrSpecialRef + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: [*c]u8 = @ptrCast([*c]u8, &buf); + const slice = target[0..14 :0]; + } + + // slice + comptime { + var buf = [_:0]u8{ 'a', 'b', 'c', 'd' } ++ [_]u8{undefined} ** 10; + var target: []u8 = &buf; + const slice = target[0..14 :0]; + } +} diff --git a/test/behavior/src.zig b/test/behavior/src.zig new file mode 100644 index 0000000000..27fa144e54 --- /dev/null +++ b/test/behavior/src.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "@src" { + doTheTest(); +} + +fn doTheTest() void { + const src = @src(); + + expect(src.line == 9); + expect(src.column == 17); + expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); + expect(std.mem.endsWith(u8, src.file, "src.zig")); + expect(src.fn_name[src.fn_name.len] == 0); + expect(src.file[src.file.len] == 0); +} diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig new file mode 100644 index 0000000000..aed52c53d8 --- /dev/null +++ b/test/behavior/struct.zig @@ -0,0 +1,945 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const maxInt = std.math.maxInt; +const StructWithNoFields = struct { + fn add(a: i32, b: i32) i32 { + return a + b; + } +}; +const empty_global_instance = StructWithNoFields{}; + +top_level_field: i32, + +test "top level fields" { + var instance = @This(){ + .top_level_field = 1234, + }; + instance.top_level_field += 1; + expectEqual(@as(i32, 1235), instance.top_level_field); +} + +test "call struct static method" { + const result = StructWithNoFields.add(3, 4); + expect(result == 7); +} + +test "return empty struct instance" { + _ = returnEmptyStructInstance(); +} +fn returnEmptyStructInstance() StructWithNoFields { + return empty_global_instance; +} + +const should_be_11 = StructWithNoFields.add(5, 6); + +test "invoke static method in global scope" { + expect(should_be_11 == 11); +} + +test "void struct fields" { + const foo = VoidStructFieldsFoo{ + .a = void{}, + .b = 1, + .c = void{}, + }; + expect(foo.b == 1); + expect(@sizeOf(VoidStructFieldsFoo) == 4); +} +const VoidStructFieldsFoo = struct { + a: void, + b: i32, + c: void, +}; + +test "structs" { + var foo: StructFoo = undefined; + @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo)); + foo.a += 1; + foo.b = foo.a == 1; + testFoo(foo); + testMutation(&foo); + expect(foo.c == 100); +} +const StructFoo = struct { + a: i32, + b: bool, + c: f32, +}; +fn testFoo(foo: StructFoo) void { + expect(foo.b); +} +fn testMutation(foo: *StructFoo) void { + foo.c = 100; +} + +const Node = struct { + val: Val, + next: *Node, +}; + +const Val = struct { + x: i32, +}; + +test "struct point to self" { + var root: Node = undefined; + root.val.x = 1; + + var node: Node = undefined; + node.next = &root; + node.val.x = 2; + + root.next = &node; + + expect(node.next.next.next.val.x == 1); +} + +test "struct byval assign" { + var foo1: StructFoo = undefined; + var foo2: StructFoo = undefined; + + foo1.a = 1234; + foo2.a = 0; + expect(foo2.a == 0); + foo2 = foo1; + expect(foo2.a == 1234); +} + +fn structInitializer() void { + const val = Val{ .x = 42 }; + expect(val.x == 42); +} + +test "fn call of struct field" { + const Foo = struct { + ptr: fn () i32, + }; + const S = struct { + fn aFunc() i32 { + return 13; + } + + fn callStructField(foo: Foo) i32 { + return foo.ptr(); + } + }; + + expect(S.callStructField(Foo{ .ptr = S.aFunc }) == 13); +} + +test "store member function in variable" { + const instance = MemberFnTestFoo{ .x = 1234 }; + const memberFn = MemberFnTestFoo.member; + const result = memberFn(instance); + expect(result == 1234); +} +const MemberFnTestFoo = struct { + x: i32, + fn member(foo: MemberFnTestFoo) i32 { + return foo.x; + } +}; + +test "call member function directly" { + const instance = MemberFnTestFoo{ .x = 1234 }; + const result = MemberFnTestFoo.member(instance); + expect(result == 1234); +} + +test "member functions" { + const r = MemberFnRand{ .seed = 1234 }; + expect(r.getSeed() == 1234); +} +const MemberFnRand = struct { + seed: u32, + pub fn getSeed(r: *const MemberFnRand) u32 { + return r.seed; + } +}; + +test "return struct byval from function" { + const bar = makeBar(1234, 5678); + expect(bar.y == 5678); +} +const Bar = struct { + x: i32, + y: i32, +}; +fn makeBar(x: i32, y: i32) Bar { + return Bar{ + .x = x, + .y = y, + }; +} + +test "empty struct method call" { + const es = EmptyStruct{}; + expect(es.method() == 1234); +} +const EmptyStruct = struct { + fn method(es: *const EmptyStruct) i32 { + return 1234; + } +}; + +test "return empty struct from fn" { + _ = testReturnEmptyStructFromFn(); +} +const EmptyStruct2 = struct {}; +fn testReturnEmptyStructFromFn() EmptyStruct2 { + return EmptyStruct2{}; +} + +test "pass slice of empty struct to fn" { + expect(testPassSliceOfEmptyStructToFn(&[_]EmptyStruct2{EmptyStruct2{}}) == 1); +} +fn testPassSliceOfEmptyStructToFn(slice: []const EmptyStruct2) usize { + return slice.len; +} + +const APackedStruct = packed struct { + x: u8, + y: u8, +}; + +test "packed struct" { + var foo = APackedStruct{ + .x = 1, + .y = 2, + }; + foo.y += 1; + const four = foo.x + foo.y; + expect(four == 4); +} + +const BitField1 = packed struct { + a: u3, + b: u3, + c: u2, +}; + +const bit_field_1 = BitField1{ + .a = 1, + .b = 2, + .c = 3, +}; + +test "bit field access" { + var data = bit_field_1; + expect(getA(&data) == 1); + expect(getB(&data) == 2); + expect(getC(&data) == 3); + comptime expect(@sizeOf(BitField1) == 1); + + data.b += 1; + expect(data.b == 3); + + data.a += 1; + expect(data.a == 2); + expect(data.b == 3); +} + +fn getA(data: *const BitField1) u3 { + return data.a; +} + +fn getB(data: *const BitField1) u3 { + return data.b; +} + +fn getC(data: *const BitField1) u2 { + return data.c; +} + +const Foo24Bits = packed struct { + field: u24, +}; +const Foo96Bits = packed struct { + a: u24, + b: u24, + c: u24, + d: u24, +}; + +test "packed struct 24bits" { + comptime { + expect(@sizeOf(Foo24Bits) == 4); + if (@sizeOf(usize) == 4) { + expect(@sizeOf(Foo96Bits) == 12); + } else { + expect(@sizeOf(Foo96Bits) == 16); + } + } + + var value = Foo96Bits{ + .a = 0, + .b = 0, + .c = 0, + .d = 0, + }; + value.a += 1; + expect(value.a == 1); + expect(value.b == 0); + expect(value.c == 0); + expect(value.d == 0); + + value.b += 1; + expect(value.a == 1); + expect(value.b == 1); + expect(value.c == 0); + expect(value.d == 0); + + value.c += 1; + expect(value.a == 1); + expect(value.b == 1); + expect(value.c == 1); + expect(value.d == 0); + + value.d += 1; + expect(value.a == 1); + expect(value.b == 1); + expect(value.c == 1); + expect(value.d == 1); +} + +const Foo32Bits = packed struct { + field: u24, + pad: u8, +}; + +const FooArray24Bits = packed struct { + a: u16, + b: [2]Foo32Bits, + c: u16, +}; + +// TODO revisit this test when doing https://github.com/ziglang/zig/issues/1512 +test "packed array 24bits" { + comptime { + expect(@sizeOf([9]Foo32Bits) == 9 * 4); + expect(@sizeOf(FooArray24Bits) == 2 + 2 * 4 + 2); + } + + var bytes = [_]u8{0} ** (@sizeOf(FooArray24Bits) + 1); + bytes[bytes.len - 1] = 0xaa; + const ptr = &std.mem.bytesAsSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; + expect(ptr.a == 0); + expect(ptr.b[0].field == 0); + expect(ptr.b[1].field == 0); + expect(ptr.c == 0); + + ptr.a = maxInt(u16); + expect(ptr.a == maxInt(u16)); + expect(ptr.b[0].field == 0); + expect(ptr.b[1].field == 0); + expect(ptr.c == 0); + + ptr.b[0].field = maxInt(u24); + expect(ptr.a == maxInt(u16)); + expect(ptr.b[0].field == maxInt(u24)); + expect(ptr.b[1].field == 0); + expect(ptr.c == 0); + + ptr.b[1].field = maxInt(u24); + expect(ptr.a == maxInt(u16)); + expect(ptr.b[0].field == maxInt(u24)); + expect(ptr.b[1].field == maxInt(u24)); + expect(ptr.c == 0); + + ptr.c = maxInt(u16); + expect(ptr.a == maxInt(u16)); + expect(ptr.b[0].field == maxInt(u24)); + expect(ptr.b[1].field == maxInt(u24)); + expect(ptr.c == maxInt(u16)); + + expect(bytes[bytes.len - 1] == 0xaa); +} + +const FooStructAligned = packed struct { + a: u8, + b: u8, +}; + +const FooArrayOfAligned = packed struct { + a: [2]FooStructAligned, +}; + +test "aligned array of packed struct" { + comptime { + expect(@sizeOf(FooStructAligned) == 2); + expect(@sizeOf(FooArrayOfAligned) == 2 * 2); + } + + var bytes = [_]u8{0xbb} ** @sizeOf(FooArrayOfAligned); + const ptr = &std.mem.bytesAsSlice(FooArrayOfAligned, bytes[0..])[0]; + + expect(ptr.a[0].a == 0xbb); + expect(ptr.a[0].b == 0xbb); + expect(ptr.a[1].a == 0xbb); + expect(ptr.a[1].b == 0xbb); +} + +test "runtime struct initialization of bitfield" { + const s1 = Nibbles{ + .x = x1, + .y = x1, + }; + const s2 = Nibbles{ + .x = @intCast(u4, x2), + .y = @intCast(u4, x2), + }; + + expect(s1.x == x1); + expect(s1.y == x1); + expect(s2.x == @intCast(u4, x2)); + expect(s2.y == @intCast(u4, x2)); +} + +var x1 = @as(u4, 1); +var x2 = @as(u8, 2); + +const Nibbles = packed struct { + x: u4, + y: u4, +}; + +const Bitfields = packed struct { + f1: u16, + f2: u16, + f3: u8, + f4: u8, + f5: u4, + f6: u4, + f7: u8, +}; + +test "native bit field understands endianness" { + var all: u64 = if (native_endian != .Little) + 0x1111222233445677 + else + 0x7765443322221111; + var bytes: [8]u8 = undefined; + @memcpy(&bytes, @ptrCast([*]u8, &all), 8); + var bitfields = @ptrCast(*Bitfields, &bytes).*; + + expect(bitfields.f1 == 0x1111); + expect(bitfields.f2 == 0x2222); + expect(bitfields.f3 == 0x33); + expect(bitfields.f4 == 0x44); + expect(bitfields.f5 == 0x5); + expect(bitfields.f6 == 0x6); + expect(bitfields.f7 == 0x77); +} + +test "align 1 field before self referential align 8 field as slice return type" { + const result = alloc(Expr); + expect(result.len == 0); +} + +const Expr = union(enum) { + Literal: u8, + Question: *Expr, +}; + +fn alloc(comptime T: type) []T { + return &[_]T{}; +} + +test "call method with mutable reference to struct with no fields" { + const S = struct { + fn doC(s: *const @This()) bool { + return true; + } + fn do(s: *@This()) bool { + return true; + } + }; + + var s = S{}; + expect(S.doC(&s)); + expect(s.doC()); + expect(S.do(&s)); + expect(s.do()); +} + +test "implicit cast packed struct field to const ptr" { + const LevelUpMove = packed struct { + move_id: u9, + level: u7, + + fn toInt(value: u7) u7 { + return value; + } + }; + + var lup: LevelUpMove = undefined; + lup.level = 12; + const res = LevelUpMove.toInt(lup.level); + expect(res == 12); +} + +test "pointer to packed struct member in a stack variable" { + const S = packed struct { + a: u2, + b: u2, + }; + + var s = S{ .a = 2, .b = 0 }; + var b_ptr = &s.b; + expect(s.b == 0); + b_ptr.* = 2; + expect(s.b == 2); +} + +test "non-byte-aligned array inside packed struct" { + const Foo = packed struct { + a: bool, + b: [0x16]u8, + }; + const S = struct { + fn bar(slice: []const u8) void { + expectEqualSlices(u8, slice, "abcdefghijklmnopqurstu"); + } + fn doTheTest() void { + var foo = Foo{ + .a = true, + .b = "abcdefghijklmnopqurstu".*, + }; + const value = foo.b; + bar(&value); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "packed struct with u0 field access" { + const S = packed struct { + f0: u0, + }; + var s = S{ .f0 = 0 }; + comptime expect(s.f0 == 0); +} + +const S0 = struct { + bar: S1, + + pub const S1 = struct { + value: u8, + }; + + fn init() @This() { + return S0{ .bar = S1{ .value = 123 } }; + } +}; + +var g_foo: S0 = S0.init(); + +test "access to global struct fields" { + g_foo.bar.value = 42; + expect(g_foo.bar.value == 42); +} + +test "packed struct with fp fields" { + const S = packed struct { + data: [3]f32, + + pub fn frob(self: *@This()) void { + self.data[0] += self.data[1] + self.data[2]; + self.data[1] += self.data[0] + self.data[2]; + self.data[2] += self.data[0] + self.data[1]; + } + }; + + var s: S = undefined; + s.data[0] = 1.0; + s.data[1] = 2.0; + s.data[2] = 3.0; + s.frob(); + expectEqual(@as(f32, 6.0), s.data[0]); + expectEqual(@as(f32, 11.0), s.data[1]); + expectEqual(@as(f32, 20.0), s.data[2]); +} + +test "use within struct scope" { + const S = struct { + usingnamespace struct { + pub fn inner() i32 { + return 42; + } + }; + }; + expectEqual(@as(i32, 42), S.inner()); +} + +test "default struct initialization fields" { + const S = struct { + a: i32 = 1234, + b: i32, + }; + const x = S{ + .b = 5, + }; + if (x.a + x.b != 1239) { + @compileError("it should be comptime known"); + } + var five: i32 = 5; + const y = S{ + .b = five, + }; + expectEqual(1239, x.a + x.b); +} + +test "fn with C calling convention returns struct by value" { + const S = struct { + fn entry() void { + var x = makeBar(10); + expectEqual(@as(i32, 10), x.handle); + } + + const ExternBar = extern struct { + handle: i32, + }; + + fn makeBar(t: i32) callconv(.C) ExternBar { + return ExternBar{ + .handle = t, + }; + } + }; + S.entry(); + comptime S.entry(); +} + +test "for loop over pointers to struct, getting field from struct pointer" { + const S = struct { + const Foo = struct { + name: []const u8, + }; + + var ok = true; + + fn eql(a: []const u8) bool { + return true; + } + + const ArrayList = struct { + fn toSlice(self: *ArrayList) []*Foo { + return @as([*]*Foo, undefined)[0..0]; + } + }; + + fn doTheTest() void { + var objects: ArrayList = undefined; + + for (objects.toSlice()) |obj| { + if (eql(obj.name)) { + ok = false; + } + } + + expect(ok); + } + }; + S.doTheTest(); +} + +test "zero-bit field in packed struct" { + const S = packed struct { + x: u10, + y: void, + }; + var x: S = undefined; +} + +test "struct field init with catch" { + const S = struct { + fn doTheTest() void { + var x: anyerror!isize = 1; + var req = Foo{ + .field = x catch undefined, + }; + expect(req.field == 1); + } + + pub const Foo = extern struct { + field: isize, + }; + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "packed struct with non-ABI-aligned field" { + const S = packed struct { + x: u9, + y: u183, + }; + var s: S = undefined; + s.x = 1; + s.y = 42; + expect(s.x == 1); + expect(s.y == 42); +} + +test "non-packed struct with u128 entry in union" { + const U = union(enum) { + Num: u128, + Void, + }; + + const S = struct { + f1: U, + f2: U, + }; + + var sx: S = undefined; + var s = &sx; + std.testing.expect(@ptrToInt(&s.f2) - @ptrToInt(&s.f1) == @byteOffsetOf(S, "f2")); + var v2 = U{ .Num = 123 }; + s.f2 = v2; + std.testing.expect(s.f2.Num == 123); +} + +test "packed struct field passed to generic function" { + const S = struct { + const P = packed struct { + b: u5, + g: u5, + r: u5, + a: u1, + }; + + fn genericReadPackedField(ptr: anytype) u5 { + return ptr.*; + } + }; + + var p: S.P = undefined; + p.b = 29; + var loaded = S.genericReadPackedField(&p.b); + expect(loaded == 29); +} + +test "anonymous struct literal syntax" { + const S = struct { + const Point = struct { + x: i32, + y: i32, + }; + + fn doTheTest() void { + var p: Point = .{ + .x = 1, + .y = 2, + }; + expect(p.x == 1); + expect(p.y == 2); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "fully anonymous struct" { + const S = struct { + fn doTheTest() void { + dump(.{ + .int = @as(u32, 1234), + .float = @as(f64, 12.34), + .b = true, + .s = "hi", + }); + } + fn dump(args: anytype) void { + expect(args.int == 1234); + expect(args.float == 12.34); + expect(args.b); + expect(args.s[0] == 'h'); + expect(args.s[1] == 'i'); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "fully anonymous list literal" { + const S = struct { + fn doTheTest() void { + dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi" }); + } + fn dump(args: anytype) void { + expect(args.@"0" == 1234); + expect(args.@"1" == 12.34); + expect(args.@"2"); + expect(args.@"3"[0] == 'h'); + expect(args.@"3"[1] == 'i'); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "anonymous struct literal assigned to variable" { + var vec = .{ @as(i32, 22), @as(i32, 55), @as(i32, 99) }; + expect(vec.@"0" == 22); + expect(vec.@"1" == 55); + expect(vec.@"2" == 99); +} + +test "struct with var field" { + const Point = struct { + x: anytype, + y: anytype, + }; + const pt = Point{ + .x = 1, + .y = 2, + }; + expect(pt.x == 1); + expect(pt.y == 2); +} + +test "comptime struct field" { + const T = struct { + a: i32, + comptime b: i32 = 1234, + }; + + var foo: T = undefined; + comptime expect(foo.b == 1234); +} + +test "anon struct literal field value initialized with fn call" { + const S = struct { + fn doTheTest() void { + var x = .{foo()}; + expectEqualSlices(u8, x[0], "hi"); + } + fn foo() []const u8 { + return "hi"; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "self-referencing struct via array member" { + const T = struct { + children: [1]*@This(), + }; + var x: T = undefined; + x = T{ .children = .{&x} }; + expect(x.children[0] == &x); +} + +test "struct with union field" { + const Value = struct { + ref: u32 = 2, + kind: union(enum) { + None: usize, + Bool: bool, + }, + }; + + var True = Value{ + .kind = .{ .Bool = true }, + }; + expectEqual(@as(u32, 2), True.ref); + expectEqual(true, True.kind.Bool); +} + +test "type coercion of anon struct literal to struct" { + const S = struct { + const S2 = struct { + A: u32, + B: []const u8, + C: void, + D: Foo = .{}, + }; + + const Foo = struct { + field: i32 = 1234, + }; + + fn doTheTest() void { + var y: u32 = 42; + const t0 = .{ .A = 123, .B = "foo", .C = {} }; + const t1 = .{ .A = y, .B = "foo", .C = {} }; + const y0: S2 = t0; + var y1: S2 = t1; + expect(y0.A == 123); + expect(std.mem.eql(u8, y0.B, "foo")); + expect(y0.C == {}); + expect(y0.D.field == 1234); + expect(y1.A == y); + expect(std.mem.eql(u8, y1.B, "foo")); + expect(y1.C == {}); + expect(y1.D.field == 1234); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "type coercion of pointer to anon struct literal to pointer to struct" { + const S = struct { + const S2 = struct { + A: u32, + B: []const u8, + C: void, + D: Foo = .{}, + }; + + const Foo = struct { + field: i32 = 1234, + }; + + fn doTheTest() void { + var y: u32 = 42; + const t0 = &.{ .A = 123, .B = "foo", .C = {} }; + const t1 = &.{ .A = y, .B = "foo", .C = {} }; + const y0: *const S2 = t0; + var y1: *const S2 = t1; + expect(y0.A == 123); + expect(std.mem.eql(u8, y0.B, "foo")); + expect(y0.C == {}); + expect(y0.D.field == 1234); + expect(y1.A == y); + expect(std.mem.eql(u8, y1.B, "foo")); + expect(y1.C == {}); + expect(y1.D.field == 1234); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "packed struct with undefined initializers" { + const S = struct { + const P = packed struct { + a: u3, + _a: u3 = undefined, + b: u3, + _b: u3 = undefined, + c: u3, + _c: u3 = undefined, + }; + + fn doTheTest() void { + var p: P = undefined; + p = P{ .a = 2, .b = 4, .c = 6 }; + // Make sure the compiler doesn't touch the unprefixed fields. + expectEqual(@as(u3, 2), p.a); + expectEqual(@as(u3, 4), p.b); + expectEqual(@as(u3, 6), p.c); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/struct_contains_null_ptr_itself.zig b/test/behavior/struct_contains_null_ptr_itself.zig new file mode 100644 index 0000000000..991d742cec --- /dev/null +++ b/test/behavior/struct_contains_null_ptr_itself.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "struct contains null pointer which contains original struct" { + var x: ?*NodeLineComment = null; + expect(x == null); +} + +pub const Node = struct { + id: Id, + comment: ?*NodeLineComment, + + pub const Id = enum { + Root, + LineComment, + }; +}; + +pub const NodeLineComment = struct { + base: Node, +}; diff --git a/test/behavior/struct_contains_slice_of_itself.zig b/test/behavior/struct_contains_slice_of_itself.zig new file mode 100644 index 0000000000..14bf0320a2 --- /dev/null +++ b/test/behavior/struct_contains_slice_of_itself.zig @@ -0,0 +1,85 @@ +const expect = @import("std").testing.expect; + +const Node = struct { + payload: i32, + children: []Node, +}; + +const NodeAligned = struct { + payload: i32, + children: []align(@alignOf(NodeAligned)) NodeAligned, +}; + +test "struct contains slice of itself" { + var other_nodes = [_]Node{ + Node{ + .payload = 31, + .children = &[_]Node{}, + }, + Node{ + .payload = 32, + .children = &[_]Node{}, + }, + }; + var nodes = [_]Node{ + Node{ + .payload = 1, + .children = &[_]Node{}, + }, + Node{ + .payload = 2, + .children = &[_]Node{}, + }, + Node{ + .payload = 3, + .children = other_nodes[0..], + }, + }; + const root = Node{ + .payload = 1234, + .children = nodes[0..], + }; + expect(root.payload == 1234); + expect(root.children[0].payload == 1); + expect(root.children[1].payload == 2); + expect(root.children[2].payload == 3); + expect(root.children[2].children[0].payload == 31); + expect(root.children[2].children[1].payload == 32); +} + +test "struct contains aligned slice of itself" { + var other_nodes = [_]NodeAligned{ + NodeAligned{ + .payload = 31, + .children = &[_]NodeAligned{}, + }, + NodeAligned{ + .payload = 32, + .children = &[_]NodeAligned{}, + }, + }; + var nodes = [_]NodeAligned{ + NodeAligned{ + .payload = 1, + .children = &[_]NodeAligned{}, + }, + NodeAligned{ + .payload = 2, + .children = &[_]NodeAligned{}, + }, + NodeAligned{ + .payload = 3, + .children = other_nodes[0..], + }, + }; + const root = NodeAligned{ + .payload = 1234, + .children = nodes[0..], + }; + expect(root.payload == 1234); + expect(root.children[0].payload == 1); + expect(root.children[1].payload == 2); + expect(root.children[2].payload == 3); + expect(root.children[2].children[0].payload == 31); + expect(root.children[2].children[1].payload == 32); +} diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig new file mode 100644 index 0000000000..20ca0d3146 --- /dev/null +++ b/test/behavior/switch.zig @@ -0,0 +1,537 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectError = std.testing.expectError; +const expectEqual = std.testing.expectEqual; + +test "switch with numbers" { + testSwitchWithNumbers(13); +} + +fn testSwitchWithNumbers(x: u32) void { + const result = switch (x) { + 1, 2, 3, 4...8 => false, + 13 => true, + else => false, + }; + expect(result); +} + +test "switch with all ranges" { + expect(testSwitchWithAllRanges(50, 3) == 1); + expect(testSwitchWithAllRanges(101, 0) == 2); + expect(testSwitchWithAllRanges(300, 5) == 3); + 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 { + 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" { + nonConstSwitch(SwitchStatmentFoo.C); +} +fn nonConstSwitch(foo: SwitchStatmentFoo) void { + const val = switch (foo) { + SwitchStatmentFoo.A => @as(i32, 1), + SwitchStatmentFoo.B => 2, + SwitchStatmentFoo.C => 3, + SwitchStatmentFoo.D => 4, + }; + expect(val == 3); +} +const SwitchStatmentFoo = enum { + A, + B, + C, + D, +}; + +test "switch prong with variable" { + switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 }); + switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 }); + switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} }); +} +const SwitchProngWithVarEnum = union(enum) { + One: i32, + Two: f32, + Meh: void, +}; +fn switchProngWithVarFn(a: SwitchProngWithVarEnum) void { + switch (a) { + SwitchProngWithVarEnum.One => |x| { + expect(x == 13); + }, + SwitchProngWithVarEnum.Two => |x| { + expect(x == 13.0); + }, + SwitchProngWithVarEnum.Meh => |x| { + const v: void = x; + }, + } +} + +test "switch on enum using pointer capture" { + testSwitchEnumPtrCapture(); + comptime testSwitchEnumPtrCapture(); +} + +fn testSwitchEnumPtrCapture() void { + var value = SwitchProngWithVarEnum{ .One = 1234 }; + switch (value) { + SwitchProngWithVarEnum.One => |*x| x.* += 1, + else => unreachable, + } + switch (value) { + SwitchProngWithVarEnum.One => |x| 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), + }; + 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" { + expect(!returnsFalse()); +} + +test "switch on type" { + expect(trueIfBoolFalseOtherwise(bool)); + expect(!trueIfBoolFalseOtherwise(i32)); +} + +fn trueIfBoolFalseOtherwise(comptime T: type) bool { + return switch (T) { + bool => true, + else => false, + }; +} + +test "switch handles all cases of number" { + testSwitchHandleAllCases(); + comptime testSwitchHandleAllCases(); +} + +fn testSwitchHandleAllCases() void { + expect(testSwitchHandleAllCasesExhaustive(0) == 3); + expect(testSwitchHandleAllCasesExhaustive(1) == 2); + expect(testSwitchHandleAllCasesExhaustive(2) == 1); + expect(testSwitchHandleAllCasesExhaustive(3) == 0); + + expect(testSwitchHandleAllCasesRange(100) == 0); + expect(testSwitchHandleAllCasesRange(200) == 1); + expect(testSwitchHandleAllCasesRange(201) == 2); + expect(testSwitchHandleAllCasesRange(202) == 4); + 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" { + testAllProngsUnreachable(); + comptime testAllProngsUnreachable(); +} + +fn testAllProngsUnreachable() void { + expect(switchWithUnreachable(1) == 2); + 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, + }; + expect(x == 1); +} + +test "switching on booleans" { + testSwitchOnBools(); + comptime testSwitchOnBools(); +} + +fn testSwitchOnBools() void { + expect(testSwitchOnBoolsTrueAndFalse(true) == false); + expect(testSwitchOnBoolsTrueAndFalse(false) == true); + + expect(testSwitchOnBoolsTrueWithElse(true) == false); + expect(testSwitchOnBoolsTrueWithElse(false) == true); + + expect(testSwitchOnBoolsFalseWithElse(true) == false); + 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 => expect(val == 0), + } +} + +test "undefined.u0" { + var val: u0 = undefined; + switch (val) { + 0 => 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| { + expect(x == 1234); + }, + } +} + +test "else prong of switch on error set excludes other cases" { + const S = struct { + fn doTheTest() void { + 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, + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "switch prongs with error set cases make a new error set type for capture value" { + const S = struct { + fn doTheTest() void { + 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 => {}, + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "return result loc and then switch with range implicit casted to error union" { + const S = struct { + fn doTheTest() void { + expect((func(0xb) catch unreachable) == 0xb); + } + fn func(d: u8) anyerror!u8 { + return switch (d) { + 0xa...0xf => d, + else => unreachable, + }; + } + }; + S.doTheTest(); + comptime 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| { + @panic("fail"); + } + } + }; + S.doTheTest(1); + comptime 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 { + doTheSwitch1(Union{ .A = 8 }); + doTheSwitch2(Union{ .B = -8 }); + } + fn doTheSwitch1(u: Union) void { + switch (u) { + .A, .C => |e| { + expect(@TypeOf(e) == usize); + expect(e == 8); + }, + .B => |e| @panic("fail"), + } + } + fn doTheSwitch2(u: Union) void { + switch (u) { + .A, .C => |e| @panic("fail"), + .B => |e| { + expect(@TypeOf(e) == isize); + expect(e == -8); + }, + } + } + }; + S.doTheTest(); + comptime 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; + doTheSwitch(u); + comptime doTheSwitch(u); + var v: u8 = 42; + doTheSwitch(v); + comptime doTheSwitch(v); + } + fn doTheSwitch(q: u8) void { + switch (q) { + 0...40 => |x| expect(x == 16), + 41, 42, 43 => |x| expect(x == 42), + else => expect(false), + } + } + }; +} + +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, + } + } + }; + + expect(1 == S.doTheTest(S.P1)); + expect(2 == S.doTheTest(S.P2)); + expect(3 == S.doTheTest(S.P3)); + comptime expect(1 == S.doTheTest(S.P1)); + comptime expect(2 == S.doTheTest(S.P2)); + comptime 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; + expect(switch (some) { + else => |a| true, + }); + } + }; + + S.doTheTest(); + comptime 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 }; + expectEqual(@as(u8, 42), value); + }, + else => unreachable, + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/switch_prong_err_enum.zig b/test/behavior/switch_prong_err_enum.zig new file mode 100644 index 0000000000..3593eabb5a --- /dev/null +++ b/test/behavior/switch_prong_err_enum.zig @@ -0,0 +1,30 @@ +const expect = @import("std").testing.expect; + +var read_count: u64 = 0; + +fn readOnce() anyerror!u64 { + read_count += 1; + return read_count; +} + +const FormValue = union(enum) { + Address: u64, + Other: bool, +}; + +fn doThing(form_id: u64) anyerror!FormValue { + return switch (form_id) { + 17 => FormValue{ .Address = try readOnce() }, + else => error.InvalidDebugInfo, + }; +} + +test "switch prong returns error enum" { + switch (doThing(17) catch unreachable) { + FormValue.Address => |payload| { + expect(payload == 1); + }, + else => unreachable, + } + expect(read_count == 1); +} diff --git a/test/behavior/switch_prong_implicit_cast.zig b/test/behavior/switch_prong_implicit_cast.zig new file mode 100644 index 0000000000..da965915ca --- /dev/null +++ b/test/behavior/switch_prong_implicit_cast.zig @@ -0,0 +1,22 @@ +const expect = @import("std").testing.expect; + +const FormValue = union(enum) { + One: void, + Two: bool, +}; + +fn foo(id: u64) !FormValue { + return switch (id) { + 2 => FormValue{ .Two = true }, + 1 => FormValue{ .One = {} }, + else => return error.Whatever, + }; +} + +test "switch prong implicit cast" { + const result = switch (foo(2) catch unreachable) { + FormValue.One => false, + FormValue.Two => |x| x, + }; + expect(result); +} diff --git a/test/behavior/syntax.zig b/test/behavior/syntax.zig new file mode 100644 index 0000000000..12df8a4315 --- /dev/null +++ b/test/behavior/syntax.zig @@ -0,0 +1,68 @@ +// Test trailing comma syntax +// zig fmt: off + +extern var a: c_int; +extern "c" var b: c_int; +export var c: c_int = 0; +threadlocal var d: c_int; +extern threadlocal var e: c_int; +extern "c" threadlocal var f: c_int; +export threadlocal var g: c_int = 0; + +const struct_trailing_comma = struct { x: i32, y: i32, }; +const struct_no_comma = struct { x: i32, y: i32 }; +const struct_fn_no_comma = struct { fn m() void {} y: i32 }; + +const enum_no_comma = enum { A, B }; + +fn container_init() void { + const S = struct { x: i32, y: i32 }; + _ = S { .x = 1, .y = 2 }; + _ = S { .x = 1, .y = 2, }; +} + +fn type_expr_return1() if (true) A {} +fn type_expr_return2() for (true) |_| A {} +fn type_expr_return3() while (true) A {} +fn type_expr_return4() comptime A {} + +fn switch_cases(x: i32) void { + switch (x) { + 1,2,3 => {}, + 4,5, => {}, + 6...8, => {}, + else => {}, + } +} + +fn switch_prongs(x: i32) void { + switch (x) { + 0 => {}, + else => {}, + } + switch (x) { + 0 => {}, + else => {} + } +} + +const fn_no_comma = fn(i32, i32)void; +const fn_trailing_comma = fn(i32, i32,)void; + +fn fn_calls() void { + fn add(x: i32, y: i32,) i32 { x + y }; + _ = add(1, 2); + _ = add(1, 2,); +} + +fn asm_lists() void { + if (false) { // Build AST but don't analyze + asm ("not real assembly" + :[a] "x" (x),); + asm ("not real assembly" + :[a] "x" (->i32),:[a] "x" (1),); + asm ("still not real assembly" + :::"a","b",); + } +} + diff --git a/test/behavior/this.zig b/test/behavior/this.zig new file mode 100644 index 0000000000..927c0808ea --- /dev/null +++ b/test/behavior/this.zig @@ -0,0 +1,34 @@ +const expect = @import("std").testing.expect; + +const module = @This(); + +fn Point(comptime T: type) type { + return struct { + const Self = @This(); + x: T, + y: T, + + fn addOne(self: *Self) void { + self.x += 1; + self.y += 1; + } + }; +} + +fn add(x: i32, y: i32) i32 { + return x + y; +} + +test "this refer to module call private fn" { + expect(module.add(1, 2) == 3); +} + +test "this refer to container" { + var pt = Point(i32){ + .x = 12, + .y = 34, + }; + pt.addOne(); + expect(pt.x == 13); + expect(pt.y == 35); +} diff --git a/test/behavior/translate_c_macros.h b/test/behavior/translate_c_macros.h new file mode 100644 index 0000000000..d3a6ac1ea5 --- /dev/null +++ b/test/behavior/translate_c_macros.h @@ -0,0 +1,18 @@ +// initializer list expression +typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +} Color; +#define CLITERAL(type) (type) +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray + +#define MY_SIZEOF(x) ((int)sizeof(x)) +#define MY_SIZEOF2(x) ((int)sizeof x) + +struct Foo { + int a; +}; + +#define SIZE_OF_FOO sizeof(struct Foo) diff --git a/test/behavior/translate_c_macros.zig b/test/behavior/translate_c_macros.zig new file mode 100644 index 0000000000..bc0d3b583e --- /dev/null +++ b/test/behavior/translate_c_macros.zig @@ -0,0 +1,22 @@ +const expect = @import("std").testing.expect; +const expectEqual = @import("std").testing.expectEqual; + +const h = @cImport(@cInclude("behavior/translate_c_macros.h")); + +test "initializer list expression" { + expectEqual(h.Color{ + .r = 200, + .g = 200, + .b = 200, + .a = 255, + }, h.LIGHTGRAY); +} + +test "sizeof in macros" { + expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF(u32)); + expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF2(u32)); +} + +test "reference to a struct type" { + expectEqual(@sizeOf(h.struct_Foo), h.SIZE_OF_FOO); +} diff --git a/test/behavior/truncate.zig b/test/behavior/truncate.zig new file mode 100644 index 0000000000..099b6c3359 --- /dev/null +++ b/test/behavior/truncate.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "truncate u0 to larger integer allowed and has comptime known result" { + var x: u0 = 0; + const y = @truncate(u8, x); + comptime expect(y == 0); +} + +test "truncate.u0.literal" { + var z = @truncate(u0, 0); + expect(z == 0); +} + +test "truncate.u0.const" { + const c0: usize = 0; + var z = @truncate(u0, c0); + expect(z == 0); +} + +test "truncate.u0.var" { + var d: u8 = 2; + var z = @truncate(u0, d); + expect(z == 0); +} + +test "truncate sign mismatch but comptime known so it works anyway" { + const x: u32 = 10; + var result = @truncate(i8, x); + expect(result == 10); +} + +test "truncate on comptime integer" { + var x = @truncate(u16, 9999); + expect(x == 9999); +} diff --git a/test/behavior/try.zig b/test/behavior/try.zig new file mode 100644 index 0000000000..9e93183c3b --- /dev/null +++ b/test/behavior/try.zig @@ -0,0 +1,43 @@ +const expect = @import("std").testing.expect; + +test "try on error union" { + tryOnErrorUnionImpl(); + comptime tryOnErrorUnionImpl(); +} + +fn tryOnErrorUnionImpl() void { + const x = if (returnsTen()) |val| val + 1 else |err| switch (err) { + error.ItBroke, error.NoMem => 1, + error.CrappedOut => @as(i32, 2), + else => unreachable, + }; + expect(x == 11); +} + +fn returnsTen() anyerror!i32 { + return 10; +} + +test "try without vars" { + const result1 = if (failIfTrue(true)) 1 else |_| @as(i32, 2); + expect(result1 == 2); + + const result2 = if (failIfTrue(false)) 1 else |_| @as(i32, 2); + expect(result2 == 1); +} + +fn failIfTrue(ok: bool) anyerror!void { + if (ok) { + return error.ItBroke; + } else { + return; + } +} + +test "try then not executed with assignment" { + if (failIfTrue(true)) { + unreachable; + } else |err| { + expect(err == error.ItBroke); + } +} diff --git a/test/behavior/tuple.zig b/test/behavior/tuple.zig new file mode 100644 index 0000000000..4eb5b73abb --- /dev/null +++ b/test/behavior/tuple.zig @@ -0,0 +1,113 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "tuple concatenation" { + const S = struct { + fn doTheTest() void { + var a: i32 = 1; + var b: i32 = 2; + var x = .{a}; + var y = .{b}; + var c = x ++ y; + expectEqual(@as(i32, 1), c[0]); + expectEqual(@as(i32, 2), c[1]); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "tuple multiplication" { + const S = struct { + fn doTheTest() void { + { + const t = .{} ** 4; + expectEqual(0, @typeInfo(@TypeOf(t)).Struct.fields.len); + } + { + const t = .{'a'} ** 4; + expectEqual(4, @typeInfo(@TypeOf(t)).Struct.fields.len); + inline for (t) |x| expectEqual('a', x); + } + { + const t = .{ 1, 2, 3 } ** 4; + expectEqual(12, @typeInfo(@TypeOf(t)).Struct.fields.len); + inline for (t) |x, i| expectEqual(1 + i % 3, x); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); + + const T = struct { + fn consume_tuple(tuple: anytype, len: usize) void { + expect(tuple.len == len); + } + + fn doTheTest() void { + const t1 = .{}; + + var rt_var: u8 = 42; + const t2 = .{rt_var} ++ .{}; + + expect(t2.len == 1); + expect(t2.@"0" == rt_var); + expect(t2.@"0" == 42); + expect(&t2.@"0" != &rt_var); + + consume_tuple(t1 ++ t1, 0); + consume_tuple(.{} ++ .{}, 0); + consume_tuple(.{0} ++ .{}, 1); + consume_tuple(.{0} ++ .{1}, 2); + consume_tuple(.{ 0, 1, 2 } ++ .{ u8, 1, noreturn }, 6); + consume_tuple(t2 ++ t1, 1); + consume_tuple(t1 ++ t2, 1); + consume_tuple(t2 ++ t2, 2); + consume_tuple(.{rt_var} ++ .{}, 1); + consume_tuple(.{rt_var} ++ t1, 1); + consume_tuple(.{} ++ .{rt_var}, 1); + consume_tuple(t2 ++ .{void}, 2); + consume_tuple(t2 ++ .{0}, 2); + consume_tuple(.{0} ++ t2, 2); + consume_tuple(.{void} ++ t2, 2); + consume_tuple(.{u8} ++ .{rt_var} ++ .{true}, 3); + } + }; + + T.doTheTest(); + comptime T.doTheTest(); +} + +test "pass tuple to comptime var parameter" { + const S = struct { + fn Foo(comptime args: anytype) void { + expect(args[0] == 1); + } + + fn doTheTest() void { + Foo(.{1}); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "tuple initializer for var" { + const S = struct { + fn doTheTest() void { + const Bytes = struct { + id: usize, + }; + + var tmp = .{ + .id = @as(usize, 2), + .name = Bytes{ .id = 20 }, + }; + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/type.zig b/test/behavior/type.zig new file mode 100644 index 0000000000..60c9117991 --- /dev/null +++ b/test/behavior/type.zig @@ -0,0 +1,453 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const TypeInfo = std.builtin.TypeInfo; +const testing = std.testing; + +fn testTypes(comptime types: []const type) void { + inline for (types) |testType| { + testing.expect(testType == @Type(@typeInfo(testType))); + } +} + +test "Type.MetaType" { + testing.expect(type == @Type(TypeInfo{ .Type = undefined })); + testTypes(&[_]type{type}); +} + +test "Type.Void" { + testing.expect(void == @Type(TypeInfo{ .Void = undefined })); + testTypes(&[_]type{void}); +} + +test "Type.Bool" { + testing.expect(bool == @Type(TypeInfo{ .Bool = undefined })); + testTypes(&[_]type{bool}); +} + +test "Type.NoReturn" { + testing.expect(noreturn == @Type(TypeInfo{ .NoReturn = undefined })); + testTypes(&[_]type{noreturn}); +} + +test "Type.Int" { + testing.expect(u1 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .unsigned, .bits = 1 } })); + testing.expect(i1 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .signed, .bits = 1 } })); + testing.expect(u8 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .unsigned, .bits = 8 } })); + testing.expect(i8 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .signed, .bits = 8 } })); + testing.expect(u64 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .unsigned, .bits = 64 } })); + testing.expect(i64 == @Type(TypeInfo{ .Int = TypeInfo.Int{ .signedness = .signed, .bits = 64 } })); + testTypes(&[_]type{ u8, u32, i64 }); +} + +test "Type.Float" { + testing.expect(f16 == @Type(TypeInfo{ .Float = TypeInfo.Float{ .bits = 16 } })); + testing.expect(f32 == @Type(TypeInfo{ .Float = TypeInfo.Float{ .bits = 32 } })); + testing.expect(f64 == @Type(TypeInfo{ .Float = TypeInfo.Float{ .bits = 64 } })); + testing.expect(f128 == @Type(TypeInfo{ .Float = TypeInfo.Float{ .bits = 128 } })); + testTypes(&[_]type{ f16, f32, f64, f128 }); +} + +test "Type.Pointer" { + testTypes(&[_]type{ + // One Value Pointer Types + *u8, *const u8, + *volatile u8, *const volatile u8, + *align(4) u8, *align(4) const u8, + *align(4) volatile u8, *align(4) const volatile u8, + *align(8) u8, *align(8) const u8, + *align(8) volatile u8, *align(8) const volatile u8, + *allowzero u8, *allowzero const u8, + *allowzero volatile u8, *allowzero const volatile u8, + *allowzero align(4) u8, *allowzero align(4) const u8, + *allowzero align(4) volatile u8, *allowzero align(4) const volatile u8, + // Many Values Pointer Types + [*]u8, [*]const u8, + [*]volatile u8, [*]const volatile u8, + [*]align(4) u8, [*]align(4) const u8, + [*]align(4) volatile u8, [*]align(4) const volatile u8, + [*]align(8) u8, [*]align(8) const u8, + [*]align(8) volatile u8, [*]align(8) const volatile u8, + [*]allowzero u8, [*]allowzero const u8, + [*]allowzero volatile u8, [*]allowzero const volatile u8, + [*]allowzero align(4) u8, [*]allowzero align(4) const u8, + [*]allowzero align(4) volatile u8, [*]allowzero align(4) const volatile u8, + // Slice Types + []u8, []const u8, + []volatile u8, []const volatile u8, + []align(4) u8, []align(4) const u8, + []align(4) volatile u8, []align(4) const volatile u8, + []align(8) u8, []align(8) const u8, + []align(8) volatile u8, []align(8) const volatile u8, + []allowzero u8, []allowzero const u8, + []allowzero volatile u8, []allowzero const volatile u8, + []allowzero align(4) u8, []allowzero align(4) const u8, + []allowzero align(4) volatile u8, []allowzero align(4) const volatile u8, + // C Pointer Types + [*c]u8, [*c]const u8, + [*c]volatile u8, [*c]const volatile u8, + [*c]align(4) u8, [*c]align(4) const u8, + [*c]align(4) volatile u8, [*c]align(4) const volatile u8, + [*c]align(8) u8, [*c]align(8) const u8, + [*c]align(8) volatile u8, [*c]align(8) const volatile u8, + }); +} + +test "Type.Array" { + testing.expect([123]u8 == @Type(TypeInfo{ + .Array = TypeInfo.Array{ + .len = 123, + .child = u8, + .sentinel = null, + }, + })); + testing.expect([2]u32 == @Type(TypeInfo{ + .Array = TypeInfo.Array{ + .len = 2, + .child = u32, + .sentinel = null, + }, + })); + testing.expect([2:0]u32 == @Type(TypeInfo{ + .Array = TypeInfo.Array{ + .len = 2, + .child = u32, + .sentinel = 0, + }, + })); + testTypes(&[_]type{ [1]u8, [30]usize, [7]bool }); +} + +test "Type.ComptimeFloat" { + testTypes(&[_]type{comptime_float}); +} +test "Type.ComptimeInt" { + testTypes(&[_]type{comptime_int}); +} +test "Type.Undefined" { + testTypes(&[_]type{@TypeOf(undefined)}); +} +test "Type.Null" { + testTypes(&[_]type{@TypeOf(null)}); +} +test "@Type create slice with null sentinel" { + const Slice = @Type(TypeInfo{ + .Pointer = .{ + .size = .Slice, + .is_const = true, + .is_volatile = false, + .is_allowzero = false, + .alignment = 8, + .child = *i32, + .sentinel = null, + }, + }); + testing.expect(Slice == []align(8) const *i32); +} +test "@Type picks up the sentinel value from TypeInfo" { + testTypes(&[_]type{ + [11:0]u8, [4:10]u8, + [*:0]u8, [*:0]const u8, + [*:0]volatile u8, [*:0]const volatile u8, + [*:0]align(4) u8, [*:0]align(4) const u8, + [*:0]align(4) volatile u8, [*:0]align(4) const volatile u8, + [*:0]align(8) u8, [*:0]align(8) const u8, + [*:0]align(8) volatile u8, [*:0]align(8) const volatile u8, + [*:0]allowzero u8, [*:0]allowzero const u8, + [*:0]allowzero volatile u8, [*:0]allowzero const volatile u8, + [*:0]allowzero align(4) u8, [*:0]allowzero align(4) const u8, + [*:0]allowzero align(4) volatile u8, [*:0]allowzero align(4) const volatile u8, + [*:5]allowzero align(4) volatile u8, [*:5]allowzero align(4) const volatile u8, + [:0]u8, [:0]const u8, + [:0]volatile u8, [:0]const volatile u8, + [:0]align(4) u8, [:0]align(4) const u8, + [:0]align(4) volatile u8, [:0]align(4) const volatile u8, + [:0]align(8) u8, [:0]align(8) const u8, + [:0]align(8) volatile u8, [:0]align(8) const volatile u8, + [:0]allowzero u8, [:0]allowzero const u8, + [:0]allowzero volatile u8, [:0]allowzero const volatile u8, + [:0]allowzero align(4) u8, [:0]allowzero align(4) const u8, + [:0]allowzero align(4) volatile u8, [:0]allowzero align(4) const volatile u8, + [:4]allowzero align(4) volatile u8, [:4]allowzero align(4) const volatile u8, + }); +} + +test "Type.Optional" { + testTypes(&[_]type{ + ?u8, + ?*u8, + ?[]u8, + ?[*]u8, + ?[*c]u8, + }); +} + +test "Type.ErrorUnion" { + testTypes(&[_]type{ + error{}!void, + error{Error}!void, + }); +} + +test "Type.Opaque" { + const Opaque = @Type(.{ + .Opaque = .{ + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + testing.expect(Opaque != opaque {}); + testing.expectEqualSlices( + TypeInfo.Declaration, + &[_]TypeInfo.Declaration{}, + @typeInfo(Opaque).Opaque.decls, + ); +} + +test "Type.Vector" { + testTypes(&[_]type{ + @Vector(0, u8), + @Vector(4, u8), + @Vector(8, *u8), + std.meta.Vector(0, u8), + std.meta.Vector(4, u8), + std.meta.Vector(8, *u8), + }); +} + +test "Type.AnyFrame" { + testTypes(&[_]type{ + anyframe, + anyframe->u8, + anyframe->anyframe->u8, + }); +} + +test "Type.EnumLiteral" { + testTypes(&[_]type{ + @TypeOf(.Dummy), + }); +} + +fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "Type.Frame" { + testTypes(&[_]type{ + @Frame(add), + }); +} + +test "Type.ErrorSet" { + // error sets don't compare equal so just check if they compile + _ = @Type(@typeInfo(error{})); + _ = @Type(@typeInfo(error{A})); + _ = @Type(@typeInfo(error{ A, B, C })); +} + +test "Type.Struct" { + const A = @Type(@typeInfo(struct { x: u8, y: u32 })); + const infoA = @typeInfo(A).Struct; + testing.expectEqual(TypeInfo.ContainerLayout.Auto, infoA.layout); + testing.expectEqualSlices(u8, "x", infoA.fields[0].name); + testing.expectEqual(u8, infoA.fields[0].field_type); + testing.expectEqual(@as(?u8, null), infoA.fields[0].default_value); + testing.expectEqualSlices(u8, "y", infoA.fields[1].name); + testing.expectEqual(u32, infoA.fields[1].field_type); + testing.expectEqual(@as(?u32, null), infoA.fields[1].default_value); + testing.expectEqualSlices(TypeInfo.Declaration, &[_]TypeInfo.Declaration{}, infoA.decls); + testing.expectEqual(@as(bool, false), infoA.is_tuple); + + var a = A{ .x = 0, .y = 1 }; + testing.expectEqual(@as(u8, 0), a.x); + testing.expectEqual(@as(u32, 1), a.y); + a.y += 1; + testing.expectEqual(@as(u32, 2), a.y); + + const B = @Type(@typeInfo(extern struct { x: u8, y: u32 = 5 })); + const infoB = @typeInfo(B).Struct; + testing.expectEqual(TypeInfo.ContainerLayout.Extern, infoB.layout); + testing.expectEqualSlices(u8, "x", infoB.fields[0].name); + testing.expectEqual(u8, infoB.fields[0].field_type); + testing.expectEqual(@as(?u8, null), infoB.fields[0].default_value); + testing.expectEqualSlices(u8, "y", infoB.fields[1].name); + testing.expectEqual(u32, infoB.fields[1].field_type); + testing.expectEqual(@as(?u32, 5), infoB.fields[1].default_value); + testing.expectEqual(@as(usize, 0), infoB.decls.len); + testing.expectEqual(@as(bool, false), infoB.is_tuple); + + const C = @Type(@typeInfo(packed struct { x: u8 = 3, y: u32 = 5 })); + const infoC = @typeInfo(C).Struct; + testing.expectEqual(TypeInfo.ContainerLayout.Packed, infoC.layout); + testing.expectEqualSlices(u8, "x", infoC.fields[0].name); + testing.expectEqual(u8, infoC.fields[0].field_type); + testing.expectEqual(@as(?u8, 3), infoC.fields[0].default_value); + testing.expectEqualSlices(u8, "y", infoC.fields[1].name); + testing.expectEqual(u32, infoC.fields[1].field_type); + testing.expectEqual(@as(?u32, 5), infoC.fields[1].default_value); + testing.expectEqual(@as(usize, 0), infoC.decls.len); + testing.expectEqual(@as(bool, false), infoC.is_tuple); +} + +test "Type.Enum" { + const Foo = @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = u8, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "a", .value = 1 }, + .{ .name = "b", .value = 5 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = true, + }, + }); + testing.expectEqual(true, @typeInfo(Foo).Enum.is_exhaustive); + testing.expectEqual(@as(u8, 1), @enumToInt(Foo.a)); + testing.expectEqual(@as(u8, 5), @enumToInt(Foo.b)); + const Bar = @Type(.{ + .Enum = .{ + .layout = .Extern, + .tag_type = u32, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "a", .value = 1 }, + .{ .name = "b", .value = 5 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = false, + }, + }); + testing.expectEqual(false, @typeInfo(Bar).Enum.is_exhaustive); + testing.expectEqual(@as(u32, 1), @enumToInt(Bar.a)); + testing.expectEqual(@as(u32, 5), @enumToInt(Bar.b)); + testing.expectEqual(@as(u32, 6), @enumToInt(@intToEnum(Bar, 6))); +} + +test "Type.Union" { + const Untagged = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = null, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "int", .field_type = i32, .alignment = @alignOf(f32) }, + .{ .name = "float", .field_type = f32, .alignment = @alignOf(f32) }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + var untagged = Untagged{ .int = 1 }; + untagged.float = 2.0; + untagged.int = 3; + testing.expectEqual(@as(i32, 3), untagged.int); + + const PackedUntagged = @Type(.{ + .Union = .{ + .layout = .Packed, + .tag_type = null, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + var packed_untagged = PackedUntagged{ .signed = -1 }; + testing.expectEqual(@as(i32, -1), packed_untagged.signed); + testing.expectEqual(~@as(u32, 0), packed_untagged.unsigned); + + const Tag = @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = u1, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "signed", .value = 0 }, + .{ .name = "unsigned", .value = 1 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = true, + }, + }); + const Tagged = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = Tag, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "signed", .field_type = i32, .alignment = @alignOf(i32) }, + .{ .name = "unsigned", .field_type = u32, .alignment = @alignOf(u32) }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + var tagged = Tagged{ .signed = -1 }; + testing.expectEqual(Tag.signed, tagged); + tagged = .{ .unsigned = 1 }; + testing.expectEqual(Tag.unsigned, tagged); +} + +test "Type.Union from Type.Enum" { + const Tag = @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = u0, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "working_as_expected", .value = 0 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = true, + }, + }); + const T = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = Tag, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "working_as_expected", .field_type = u32, .alignment = @alignOf(u32) }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + _ = T; + _ = @typeInfo(T).Union; +} + +test "Type.Union from regular enum" { + const E = enum { working_as_expected = 0 }; + const T = @Type(.{ + .Union = .{ + .layout = .Auto, + .tag_type = E, + .fields = &[_]TypeInfo.UnionField{ + .{ .name = "working_as_expected", .field_type = u32, .alignment = @alignOf(u32) }, + }, + .decls = &[_]TypeInfo.Declaration{}, + }, + }); + _ = T; + _ = @typeInfo(T).Union; +} + +test "Type.Fn" { + // wasm doesn't support align attributes on functions + if (builtin.target.cpu.arch == .wasm32 or builtin.target.cpu.arch == .wasm64) return error.SkipZigTest; + + const foo = struct { + fn func(a: usize, b: bool) align(4) callconv(.C) usize { + return 0; + } + }.func; + const Foo = @Type(@typeInfo(@TypeOf(foo))); + const foo_2: Foo = foo; +} + +test "Type.BoundFn" { + // wasm doesn't support align attributes on functions + if (builtin.target.cpu.arch == .wasm32 or builtin.target.cpu.arch == .wasm64) return error.SkipZigTest; + + const TestStruct = packed struct { + pub fn foo(self: *const @This()) align(4) callconv(.Unspecified) void {} + }; + const test_instance: TestStruct = undefined; + testing.expect(std.meta.eql( + @typeName(@TypeOf(test_instance.foo)), + @typeName(@Type(@typeInfo(@TypeOf(test_instance.foo)))), + )); +} diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig new file mode 100644 index 0000000000..2315290466 --- /dev/null +++ b/test/behavior/type_info.zig @@ -0,0 +1,485 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const mem = std.mem; + +const TypeInfo = std.builtin.TypeInfo; +const TypeId = std.builtin.TypeId; + +const expect = std.testing.expect; +const expectEqualStrings = std.testing.expectEqualStrings; + +test "type info: tag type, void info" { + testBasic(); + comptime testBasic(); +} + +fn testBasic() void { + expect(@typeInfo(TypeInfo).Union.tag_type == TypeId); + const void_info = @typeInfo(void); + expect(void_info == TypeId.Void); + expect(void_info.Void == {}); +} + +test "type info: integer, floating point type info" { + testIntFloat(); + comptime testIntFloat(); +} + +fn testIntFloat() void { + const u8_info = @typeInfo(u8); + expect(u8_info == .Int); + expect(u8_info.Int.signedness == .unsigned); + expect(u8_info.Int.bits == 8); + + const f64_info = @typeInfo(f64); + expect(f64_info == .Float); + expect(f64_info.Float.bits == 64); +} + +test "type info: pointer type info" { + testPointer(); + comptime testPointer(); +} + +fn testPointer() void { + const u32_ptr_info = @typeInfo(*u32); + expect(u32_ptr_info == .Pointer); + expect(u32_ptr_info.Pointer.size == TypeInfo.Pointer.Size.One); + expect(u32_ptr_info.Pointer.is_const == false); + expect(u32_ptr_info.Pointer.is_volatile == false); + expect(u32_ptr_info.Pointer.alignment == @alignOf(u32)); + expect(u32_ptr_info.Pointer.child == u32); + expect(u32_ptr_info.Pointer.sentinel == null); +} + +test "type info: unknown length pointer type info" { + testUnknownLenPtr(); + comptime testUnknownLenPtr(); +} + +fn testUnknownLenPtr() void { + const u32_ptr_info = @typeInfo([*]const volatile f64); + expect(u32_ptr_info == .Pointer); + expect(u32_ptr_info.Pointer.size == TypeInfo.Pointer.Size.Many); + expect(u32_ptr_info.Pointer.is_const == true); + expect(u32_ptr_info.Pointer.is_volatile == true); + expect(u32_ptr_info.Pointer.sentinel == null); + expect(u32_ptr_info.Pointer.alignment == @alignOf(f64)); + expect(u32_ptr_info.Pointer.child == f64); +} + +test "type info: null terminated pointer type info" { + testNullTerminatedPtr(); + comptime testNullTerminatedPtr(); +} + +fn testNullTerminatedPtr() void { + const ptr_info = @typeInfo([*:0]u8); + expect(ptr_info == .Pointer); + expect(ptr_info.Pointer.size == TypeInfo.Pointer.Size.Many); + expect(ptr_info.Pointer.is_const == false); + expect(ptr_info.Pointer.is_volatile == false); + expect(ptr_info.Pointer.sentinel.? == 0); + + expect(@typeInfo([:0]u8).Pointer.sentinel != null); +} + +test "type info: C pointer type info" { + testCPtr(); + comptime testCPtr(); +} + +fn testCPtr() void { + const ptr_info = @typeInfo([*c]align(4) const i8); + expect(ptr_info == .Pointer); + expect(ptr_info.Pointer.size == .C); + expect(ptr_info.Pointer.is_const); + expect(!ptr_info.Pointer.is_volatile); + expect(ptr_info.Pointer.alignment == 4); + expect(ptr_info.Pointer.child == i8); +} + +test "type info: slice type info" { + testSlice(); + comptime testSlice(); +} + +fn testSlice() void { + const u32_slice_info = @typeInfo([]u32); + expect(u32_slice_info == .Pointer); + expect(u32_slice_info.Pointer.size == .Slice); + expect(u32_slice_info.Pointer.is_const == false); + expect(u32_slice_info.Pointer.is_volatile == false); + expect(u32_slice_info.Pointer.alignment == 4); + expect(u32_slice_info.Pointer.child == u32); +} + +test "type info: array type info" { + testArray(); + comptime testArray(); +} + +fn testArray() void { + { + const info = @typeInfo([42]u8); + expect(info == .Array); + expect(info.Array.len == 42); + expect(info.Array.child == u8); + expect(info.Array.sentinel == null); + } + + { + const info = @typeInfo([10:0]u8); + expect(info.Array.len == 10); + expect(info.Array.child == u8); + expect(info.Array.sentinel.? == @as(u8, 0)); + expect(@sizeOf([10:0]u8) == info.Array.len + 1); + } +} + +test "type info: optional type info" { + testOptional(); + comptime testOptional(); +} + +fn testOptional() void { + const null_info = @typeInfo(?void); + expect(null_info == .Optional); + expect(null_info.Optional.child == void); +} + +test "type info: error set, error union info" { + testErrorSet(); + comptime testErrorSet(); +} + +fn testErrorSet() void { + const TestErrorSet = error{ + First, + Second, + Third, + }; + + const error_set_info = @typeInfo(TestErrorSet); + expect(error_set_info == .ErrorSet); + expect(error_set_info.ErrorSet.?.len == 3); + expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "First")); + + const error_union_info = @typeInfo(TestErrorSet!usize); + expect(error_union_info == .ErrorUnion); + expect(error_union_info.ErrorUnion.error_set == TestErrorSet); + expect(error_union_info.ErrorUnion.payload == usize); + + const global_info = @typeInfo(anyerror); + expect(global_info == .ErrorSet); + expect(global_info.ErrorSet == null); +} + +test "type info: enum info" { + testEnum(); + comptime testEnum(); +} + +fn testEnum() void { + const Os = enum { + Windows, + Macos, + Linux, + FreeBSD, + }; + + const os_info = @typeInfo(Os); + expect(os_info == .Enum); + expect(os_info.Enum.layout == .Auto); + expect(os_info.Enum.fields.len == 4); + expect(mem.eql(u8, os_info.Enum.fields[1].name, "Macos")); + expect(os_info.Enum.fields[3].value == 3); + expect(os_info.Enum.tag_type == u2); + expect(os_info.Enum.decls.len == 0); +} + +test "type info: union info" { + testUnion(); + comptime testUnion(); +} + +fn testUnion() void { + const typeinfo_info = @typeInfo(TypeInfo); + expect(typeinfo_info == .Union); + expect(typeinfo_info.Union.layout == .Auto); + expect(typeinfo_info.Union.tag_type.? == TypeId); + expect(typeinfo_info.Union.fields.len == 25); + expect(typeinfo_info.Union.fields[4].field_type == @TypeOf(@typeInfo(u8).Int)); + expect(typeinfo_info.Union.decls.len == 22); + + const TestNoTagUnion = union { + Foo: void, + Bar: u32, + }; + + const notag_union_info = @typeInfo(TestNoTagUnion); + expect(notag_union_info == .Union); + expect(notag_union_info.Union.tag_type == null); + expect(notag_union_info.Union.layout == .Auto); + expect(notag_union_info.Union.fields.len == 2); + expect(notag_union_info.Union.fields[0].alignment == @alignOf(void)); + expect(notag_union_info.Union.fields[1].field_type == u32); + expect(notag_union_info.Union.fields[1].alignment == @alignOf(u32)); + + const TestExternUnion = extern union { + foo: *c_void, + }; + + const extern_union_info = @typeInfo(TestExternUnion); + expect(extern_union_info.Union.layout == .Extern); + expect(extern_union_info.Union.tag_type == null); + expect(extern_union_info.Union.fields[0].field_type == *c_void); +} + +test "type info: struct info" { + testStruct(); + comptime testStruct(); +} + +fn testStruct() void { + const unpacked_struct_info = @typeInfo(TestUnpackedStruct); + expect(unpacked_struct_info.Struct.is_tuple == false); + expect(unpacked_struct_info.Struct.fields[0].alignment == @alignOf(u32)); + expect(unpacked_struct_info.Struct.fields[0].default_value.? == 4); + expectEqualStrings("foobar", unpacked_struct_info.Struct.fields[1].default_value.?); + + const struct_info = @typeInfo(TestStruct); + expect(struct_info == .Struct); + expect(struct_info.Struct.is_tuple == false); + expect(struct_info.Struct.layout == .Packed); + expect(struct_info.Struct.fields.len == 4); + expect(struct_info.Struct.fields[0].alignment == 2 * @alignOf(usize)); + expect(struct_info.Struct.fields[2].field_type == *TestStruct); + expect(struct_info.Struct.fields[2].default_value == null); + expect(struct_info.Struct.fields[3].default_value.? == 4); + expect(struct_info.Struct.fields[3].alignment == 1); + expect(struct_info.Struct.decls.len == 2); + expect(struct_info.Struct.decls[0].is_pub); + expect(!struct_info.Struct.decls[0].data.Fn.is_extern); + expect(struct_info.Struct.decls[0].data.Fn.lib_name == null); + expect(struct_info.Struct.decls[0].data.Fn.return_type == void); + expect(struct_info.Struct.decls[0].data.Fn.fn_type == fn (*const TestStruct) void); +} + +const TestUnpackedStruct = struct { + fieldA: u32 = 4, + fieldB: *const [6:0]u8 = "foobar", +}; + +const TestStruct = packed struct { + fieldA: usize align(2 * @alignOf(usize)), + fieldB: void, + fieldC: *Self, + fieldD: u32 = 4, + + pub fn foo(self: *const Self) void {} + const Self = @This(); +}; + +test "type info: opaque info" { + testOpaque(); + comptime testOpaque(); +} + +fn testOpaque() void { + const Foo = opaque { + const A = 1; + fn b() void {} + }; + + const foo_info = @typeInfo(Foo); + expect(foo_info.Opaque.decls.len == 2); +} + +test "type info: function type info" { + // wasm doesn't support align attributes on functions + if (builtin.target.cpu.arch == .wasm32 or builtin.target.cpu.arch == .wasm64) return error.SkipZigTest; + testFunction(); + comptime testFunction(); +} + +fn testFunction() void { + const fn_info = @typeInfo(@TypeOf(foo)); + expect(fn_info == .Fn); + // TODO Fix this before merging the branch + //expect(fn_info.Fn.alignment > 0); + expect(fn_info.Fn.calling_convention == .C); + expect(!fn_info.Fn.is_generic); + expect(fn_info.Fn.args.len == 2); + expect(fn_info.Fn.is_var_args); + expect(fn_info.Fn.return_type.? == usize); + const fn_aligned_info = @typeInfo(@TypeOf(fooAligned)); + expect(fn_aligned_info.Fn.alignment == 4); + + const test_instance: TestStruct = undefined; + const bound_fn_info = @typeInfo(@TypeOf(test_instance.foo)); + expect(bound_fn_info == .BoundFn); + expect(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestStruct); +} + +extern fn foo(a: usize, b: bool, ...) callconv(.C) usize; +extern fn fooAligned(a: usize, b: bool, ...) align(4) callconv(.C) usize; + +test "typeInfo with comptime parameter in struct fn def" { + const S = struct { + pub fn func(comptime x: f32) void {} + }; + comptime var info = @typeInfo(S); +} + +test "type info: vectors" { + testVector(); + comptime testVector(); +} + +fn testVector() void { + const vec_info = @typeInfo(std.meta.Vector(4, i32)); + expect(vec_info == .Vector); + expect(vec_info.Vector.len == 4); + expect(vec_info.Vector.child == i32); +} + +test "type info: anyframe and anyframe->T" { + testAnyFrame(); + comptime testAnyFrame(); +} + +fn testAnyFrame() void { + { + const anyframe_info = @typeInfo(anyframe->i32); + expect(anyframe_info == .AnyFrame); + expect(anyframe_info.AnyFrame.child.? == i32); + } + + { + const anyframe_info = @typeInfo(anyframe); + expect(anyframe_info == .AnyFrame); + expect(anyframe_info.AnyFrame.child == null); + } +} + +test "type info: pass to function" { + _ = passTypeInfo(@typeInfo(void)); + _ = comptime passTypeInfo(@typeInfo(void)); +} + +fn passTypeInfo(comptime info: TypeInfo) type { + return void; +} + +test "type info: TypeId -> TypeInfo impl cast" { + _ = passTypeInfo(TypeId.Void); + _ = comptime passTypeInfo(TypeId.Void); +} + +test "type info: extern fns with and without lib names" { + const S = struct { + extern fn bar1() void; + extern "cool" fn bar2() void; + }; + const info = @typeInfo(S); + comptime { + for (info.Struct.decls) |decl| { + if (std.mem.eql(u8, decl.name, "bar1")) { + expect(decl.data.Fn.lib_name == null); + } else { + expectEqualStrings("cool", decl.data.Fn.lib_name.?); + } + } + } +} + +test "data field is a compile-time value" { + const S = struct { + const Bar = @as(isize, -1); + }; + comptime expect(@typeInfo(S).Struct.decls[0].data.Var == isize); +} + +test "sentinel of opaque pointer type" { + const c_void_info = @typeInfo(*c_void); + expect(c_void_info.Pointer.sentinel == null); +} + +test "@typeInfo does not force declarations into existence" { + const S = struct { + x: i32, + + fn doNotReferenceMe() void { + @compileError("test failed"); + } + }; + comptime expect(@typeInfo(S).Struct.fields.len == 1); +} + +test "defaut value for a var-typed field" { + const S = struct { x: anytype }; + expect(@typeInfo(S).Struct.fields[0].default_value == null); +} + +fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "type info for async frames" { + switch (@typeInfo(@Frame(add))) { + .Frame => |frame| { + expect(frame.function == add); + }, + else => unreachable, + } +} + +test "type info: value is correctly copied" { + comptime { + var ptrInfo = @typeInfo([]u32); + ptrInfo.Pointer.size = .One; + expect(@typeInfo([]u32).Pointer.size == .Slice); + } +} + +test "Declarations are returned in declaration order" { + const S = struct { + const a = 1; + const b = 2; + const c = 3; + const d = 4; + const e = 5; + }; + const d = @typeInfo(S).Struct.decls; + expect(std.mem.eql(u8, d[0].name, "a")); + expect(std.mem.eql(u8, d[1].name, "b")); + expect(std.mem.eql(u8, d[2].name, "c")); + expect(std.mem.eql(u8, d[3].name, "d")); + expect(std.mem.eql(u8, d[4].name, "e")); +} + +test "Struct.is_tuple" { + expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple); + expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple); +} + +test "StructField.is_comptime" { + const info = @typeInfo(struct { x: u8 = 3, comptime y: u32 = 5 }).Struct; + expect(!info.fields[0].is_comptime); + expect(info.fields[1].is_comptime); +} + +test "typeInfo resolves usingnamespace declarations" { + const A = struct { + pub const f1 = 42; + }; + + const B = struct { + const f0 = 42; + usingnamespace A; + }; + + expect(@typeInfo(B).Struct.decls.len == 2); + //a +} diff --git a/test/behavior/typename.zig b/test/behavior/typename.zig new file mode 100644 index 0000000000..1abefe5b5e --- /dev/null +++ b/test/behavior/typename.zig @@ -0,0 +1,7 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqualSlices = std.testing.expectEqualSlices; + +test "slice" { + expectEqualSlices(u8, "[]u8", @typeName([]u8)); +} diff --git a/test/behavior/undefined.zig b/test/behavior/undefined.zig new file mode 100644 index 0000000000..114b0262b1 --- /dev/null +++ b/test/behavior/undefined.zig @@ -0,0 +1,69 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; + +fn initStaticArray() [10]i32 { + var array: [10]i32 = undefined; + array[0] = 1; + array[4] = 2; + array[7] = 3; + array[9] = 4; + return array; +} +const static_array = initStaticArray(); +test "init static array to undefined" { + expect(static_array[0] == 1); + expect(static_array[4] == 2); + expect(static_array[7] == 3); + expect(static_array[9] == 4); + + comptime { + expect(static_array[0] == 1); + expect(static_array[4] == 2); + expect(static_array[7] == 3); + expect(static_array[9] == 4); + } +} + +const Foo = struct { + x: i32, + + fn setFooXMethod(foo: *Foo) void { + foo.x = 3; + } +}; + +fn setFooX(foo: *Foo) void { + foo.x = 2; +} + +test "assign undefined to struct" { + comptime { + var foo: Foo = undefined; + setFooX(&foo); + expect(foo.x == 2); + } + { + var foo: Foo = undefined; + setFooX(&foo); + expect(foo.x == 2); + } +} + +test "assign undefined to struct with method" { + comptime { + var foo: Foo = undefined; + foo.setFooXMethod(); + expect(foo.x == 3); + } + { + var foo: Foo = undefined; + foo.setFooXMethod(); + expect(foo.x == 3); + } +} + +test "type name of undefined" { + const x = undefined; + expect(mem.eql(u8, @typeName(@TypeOf(x)), "(undefined)")); +} diff --git a/test/behavior/underscore.zig b/test/behavior/underscore.zig new file mode 100644 index 0000000000..516d33a4eb --- /dev/null +++ b/test/behavior/underscore.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "ignore lval with underscore" { + _ = false; +} + +test "ignore lval with underscore (for loop)" { + for ([_]void{}) |_, i| { + for ([_]void{}) |_, j| { + break; + } + break; + } +} + +test "ignore lval with underscore (while loop)" { + while (optionalReturnError()) |_| { + while (optionalReturnError()) |_| { + break; + } else |_| {} + break; + } else |_| {} +} + +fn optionalReturnError() !?u32 { + return error.optionalReturnError; +} diff --git a/test/behavior/union.zig b/test/behavior/union.zig new file mode 100644 index 0000000000..e46b6bb6b9 --- /dev/null +++ b/test/behavior/union.zig @@ -0,0 +1,806 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const Tag = std.meta.Tag; + +const Value = union(enum) { + Int: u64, + Array: [9]u8, +}; + +const Agg = struct { + val1: Value, + val2: Value, +}; + +const v1 = Value{ .Int = 1234 }; +const v2 = Value{ .Array = [_]u8{3} ** 9 }; + +const err = @as(anyerror!Agg, Agg{ + .val1 = v1, + .val2 = v2, +}); + +const array = [_]Value{ + v1, + v2, + v1, + v2, +}; + +test "unions embedded in aggregate types" { + switch (array[1]) { + Value.Array => |arr| expect(arr[4] == 3), + else => unreachable, + } + switch ((err catch unreachable).val1) { + Value.Int => |x| expect(x == 1234), + else => unreachable, + } +} + +const Foo = union { + float: f64, + int: i32, +}; + +test "basic unions" { + var foo = Foo{ .int = 1 }; + expect(foo.int == 1); + foo = Foo{ .float = 12.34 }; + expect(foo.float == 12.34); +} + +test "comptime union field access" { + comptime { + var foo = Foo{ .int = 0 }; + expect(foo.int == 0); + + foo = Foo{ .float = 42.42 }; + expect(foo.float == 42.42); + } +} + +test "init union with runtime value" { + var foo: Foo = undefined; + + setFloat(&foo, 12.34); + expect(foo.float == 12.34); + + setInt(&foo, 42); + expect(foo.int == 42); +} + +fn setFloat(foo: *Foo, x: f64) void { + foo.* = Foo{ .float = x }; +} + +fn setInt(foo: *Foo, x: i32) void { + foo.* = Foo{ .int = x }; +} + +const FooExtern = extern union { + float: f64, + int: i32, +}; + +test "basic extern unions" { + var foo = FooExtern{ .int = 1 }; + expect(foo.int == 1); + foo.float = 12.34; + expect(foo.float == 12.34); +} + +const Letter = enum { + A, + B, + C, +}; +const Payload = union(Letter) { + A: i32, + B: f64, + C: bool, +}; + +test "union with specified enum tag" { + doTest(); + comptime doTest(); +} + +fn doTest() void { + expect(bar(Payload{ .A = 1234 }) == -10); +} + +fn bar(value: Payload) i32 { + expect(@as(Letter, value) == Letter.A); + return switch (value) { + Payload.A => |x| return x - 1244, + Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21, + Payload.C => |x| if (x) @as(i32, 30) else 31, + }; +} + +const MultipleChoice = union(enum(u32)) { + A = 20, + B = 40, + C = 60, + D = 1000, +}; +test "simple union(enum(u32))" { + var x = MultipleChoice.C; + expect(x == MultipleChoice.C); + expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60); +} + +const MultipleChoice2 = union(enum(u32)) { + Unspecified1: i32, + A: f32 = 20, + Unspecified2: void, + B: bool = 40, + Unspecified3: i32, + C: i8 = 60, + Unspecified4: void, + D: void = 1000, + Unspecified5: i32, +}; + +test "union(enum(u32)) with specified and unspecified tag values" { + comptime expect(Tag(Tag(MultipleChoice2)) == u32); + testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); + comptime testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); +} + +fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void { + expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60); + expect(1123 == switch (x) { + MultipleChoice2.A => 1, + MultipleChoice2.B => 2, + MultipleChoice2.C => |v| @as(i32, 1000) + v, + MultipleChoice2.D => 4, + MultipleChoice2.Unspecified1 => 5, + MultipleChoice2.Unspecified2 => 6, + MultipleChoice2.Unspecified3 => 7, + MultipleChoice2.Unspecified4 => 8, + MultipleChoice2.Unspecified5 => 9, + }); +} + +const ExternPtrOrInt = extern union { + ptr: *u8, + int: u64, +}; +test "extern union size" { + comptime expect(@sizeOf(ExternPtrOrInt) == 8); +} + +const PackedPtrOrInt = packed union { + ptr: *u8, + int: u64, +}; +test "extern union size" { + comptime expect(@sizeOf(PackedPtrOrInt) == 8); +} + +const ZeroBits = union { + OnlyField: void, +}; +test "union with only 1 field which is void should be zero bits" { + comptime expect(@sizeOf(ZeroBits) == 0); +} + +const TheTag = enum { + A, + B, + C, +}; +const TheUnion = union(TheTag) { + A: i32, + B: i32, + C: i32, +}; +test "union field access gives the enum values" { + expect(TheUnion.A == TheTag.A); + expect(TheUnion.B == TheTag.B); + expect(TheUnion.C == TheTag.C); +} + +test "cast union to tag type of union" { + testCastUnionToTag(TheUnion{ .B = 1234 }); + comptime testCastUnionToTag(TheUnion{ .B = 1234 }); +} + +fn testCastUnionToTag(x: TheUnion) void { + expect(@as(TheTag, x) == TheTag.B); +} + +test "cast tag type of union to union" { + var x: Value2 = Letter2.B; + expect(@as(Letter2, x) == Letter2.B); +} +const Letter2 = enum { + A, + B, + C, +}; +const Value2 = union(Letter2) { + A: i32, + B, + C, +}; + +test "implicit cast union to its tag type" { + var x: Value2 = Letter2.B; + expect(x == Letter2.B); + giveMeLetterB(x); +} +fn giveMeLetterB(x: Letter2) void { + expect(x == Value2.B); +} + +pub const PackThis = union(enum) { + Invalid: bool, + StringLiteral: u2, +}; + +test "constant packed union" { + testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); +} + +fn testConstPackedUnion(expected_tokens: []const PackThis) void { + expect(expected_tokens[0].StringLiteral == 1); +} + +test "switch on union with only 1 field" { + var r: PartialInst = undefined; + r = PartialInst.Compiled; + switch (r) { + PartialInst.Compiled => { + var z: PartialInstWithPayload = undefined; + z = PartialInstWithPayload{ .Compiled = 1234 }; + switch (z) { + PartialInstWithPayload.Compiled => |x| { + expect(x == 1234); + return; + }, + } + }, + } + unreachable; +} + +const PartialInst = union(enum) { + Compiled, +}; + +const PartialInstWithPayload = union(enum) { + Compiled: i32, +}; + +test "access a member of tagged union with conflicting enum tag name" { + const Bar = union(enum) { + A: A, + B: B, + + const A = u8; + const B = void; + }; + + comptime expect(Bar.A == u8); +} + +test "tagged union initialization with runtime void" { + expect(testTaggedUnionInit({})); +} + +const TaggedUnionWithAVoid = union(enum) { + A, + B: i32, +}; + +fn testTaggedUnionInit(x: anytype) bool { + const y = TaggedUnionWithAVoid{ .A = x }; + return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A; +} + +pub const UnionEnumNoPayloads = union(enum) { + A, + B, +}; + +test "tagged union with no payloads" { + const a = UnionEnumNoPayloads{ .B = {} }; + switch (a) { + Tag(UnionEnumNoPayloads).A => @panic("wrong"), + Tag(UnionEnumNoPayloads).B => {}, + } +} + +test "union with only 1 field casted to its enum type" { + const Literal = union(enum) { + Number: f64, + Bool: bool, + }; + + const Expr = union(enum) { + Literal: Literal, + }; + + var e = Expr{ .Literal = Literal{ .Bool = true } }; + const ExprTag = Tag(Expr); + comptime expect(Tag(ExprTag) == u0); + var t = @as(ExprTag, e); + expect(t == Expr.Literal); +} + +test "union with only 1 field casted to its enum type which has enum value specified" { + const Literal = union(enum) { + Number: f64, + Bool: bool, + }; + + const ExprTag = enum(comptime_int) { + Literal = 33, + }; + + const Expr = union(ExprTag) { + Literal: Literal, + }; + + var e = Expr{ .Literal = Literal{ .Bool = true } }; + comptime expect(Tag(ExprTag) == comptime_int); + var t = @as(ExprTag, e); + expect(t == Expr.Literal); + expect(@enumToInt(t) == 33); + comptime expect(@enumToInt(t) == 33); +} + +test "@enumToInt works on unions" { + const Bar = union(enum) { + A: bool, + B: u8, + C, + }; + + const a = Bar{ .A = true }; + var b = Bar{ .B = undefined }; + var c = Bar.C; + expect(@enumToInt(a) == 0); + expect(@enumToInt(b) == 1); + expect(@enumToInt(c) == 2); +} + +const Attribute = union(enum) { + A: bool, + B: u8, +}; + +fn setAttribute(attr: Attribute) void {} + +fn Setter(attr: Attribute) type { + return struct { + fn set() void { + setAttribute(attr); + } + }; +} + +test "comptime union field value equality" { + const a0 = Setter(Attribute{ .A = false }); + const a1 = Setter(Attribute{ .A = true }); + const a2 = Setter(Attribute{ .A = false }); + + const b0 = Setter(Attribute{ .B = 5 }); + const b1 = Setter(Attribute{ .B = 9 }); + const b2 = Setter(Attribute{ .B = 5 }); + + expect(a0 == a0); + expect(a1 == a1); + expect(a0 == a2); + + expect(b0 == b0); + expect(b1 == b1); + expect(b0 == b2); + + expect(a0 != b0); + expect(a0 != a1); + expect(b0 != b1); +} + +test "return union init with void payload" { + const S = struct { + fn entry() void { + expect(func().state == State.one); + } + const Outer = union(enum) { + state: State, + }; + const State = union(enum) { + one: void, + two: u32, + }; + fn func() Outer { + return Outer{ .state = State{ .one = {} } }; + } + }; + S.entry(); + comptime S.entry(); +} + +test "@unionInit can modify a union type" { + const UnionInitEnum = union(enum) { + Boolean: bool, + Byte: u8, + }; + + var value: UnionInitEnum = undefined; + + value = @unionInit(UnionInitEnum, "Boolean", true); + expect(value.Boolean == true); + value.Boolean = false; + expect(value.Boolean == false); + + value = @unionInit(UnionInitEnum, "Byte", 2); + expect(value.Byte == 2); + value.Byte = 3; + expect(value.Byte == 3); +} + +test "@unionInit can modify a pointer value" { + const UnionInitEnum = union(enum) { + Boolean: bool, + Byte: u8, + }; + + var value: UnionInitEnum = undefined; + var value_ptr = &value; + + value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true); + expect(value.Boolean == true); + + value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2); + expect(value.Byte == 2); +} + +test "union no tag with struct member" { + const Struct = struct {}; + const Union = union { + s: Struct, + pub fn foo(self: *@This()) void {} + }; + var u = Union{ .s = Struct{} }; + u.foo(); +} + +fn testComparison() void { + var x = Payload{ .A = 42 }; + expect(x == .A); + expect(x != .B); + expect(x != .C); + expect((x == .B) == false); + expect((x == .C) == false); + expect((x != .A) == false); +} + +test "comparison between union and enum literal" { + testComparison(); + comptime testComparison(); +} + +test "packed union generates correctly aligned LLVM type" { + const U = packed union { + f1: fn () void, + f2: u32, + }; + var foo = [_]U{ + U{ .f1 = doTest }, + U{ .f2 = 0 }, + }; + foo[0].f1(); +} + +test "union with one member defaults to u0 tag type" { + const U0 = union(enum) { + X: u32, + }; + comptime expect(Tag(Tag(U0)) == u0); +} + +test "union with comptime_int tag" { + const Union = union(enum(comptime_int)) { + X: u32, + Y: u16, + Z: u8, + }; + comptime expect(Tag(Tag(Union)) == comptime_int); +} + +test "extern union doesn't trigger field check at comptime" { + const U = extern union { + x: u32, + y: u8, + }; + + const x = U{ .x = 0x55AAAA55 }; + comptime expect(x.y == 0x55); +} + +const Foo1 = union(enum) { + f: struct { + x: usize, + }, +}; +var glbl: Foo1 = undefined; + +test "global union with single field is correctly initialized" { + glbl = Foo1{ + .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, + }; + expect(glbl.f.x == 123); +} + +pub const FooUnion = union(enum) { + U0: usize, + U1: u8, +}; + +var glbl_array: [2]FooUnion = undefined; + +test "initialize global array of union" { + glbl_array[1] = FooUnion{ .U1 = 2 }; + glbl_array[0] = FooUnion{ .U0 = 1 }; + expect(glbl_array[0].U0 == 1); + expect(glbl_array[1].U1 == 2); +} + +test "anonymous union literal syntax" { + const S = struct { + const Number = union { + int: i32, + float: f64, + }; + + fn doTheTest() void { + var i: Number = .{ .int = 42 }; + var f = makeNumber(); + expect(i.int == 42); + expect(f.float == 12.34); + } + + fn makeNumber() Number { + return .{ .float = 12.34 }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "update the tag value for zero-sized unions" { + const S = union(enum) { + U0: void, + U1: void, + }; + var x = S{ .U0 = {} }; + expect(x == .U0); + x = S{ .U1 = {} }; + expect(x == .U1); +} + +test "function call result coerces from tagged union to the tag" { + const S = struct { + const Arch = union(enum) { + One, + Two: usize, + }; + + const ArchTag = Tag(Arch); + + fn doTheTest() void { + var x: ArchTag = getArch1(); + expect(x == .One); + + var y: ArchTag = getArch2(); + expect(y == .Two); + } + + pub fn getArch1() Arch { + return .One; + } + + pub fn getArch2() Arch { + return .{ .Two = 99 }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "0-sized extern union definition" { + const U = extern union { + a: void, + const f = 1; + }; + + expect(U.f == 1); +} + +test "union initializer generates padding only if needed" { + const U = union(enum) { + A: u24, + }; + + var v = U{ .A = 532 }; + expect(v.A == 532); +} + +test "runtime tag name with single field" { + const U = union(enum) { + A: i32, + }; + + var v = U{ .A = 42 }; + expect(std.mem.eql(u8, @tagName(v), "A")); +} + +test "cast from anonymous struct to union" { + const S = struct { + const U = union(enum) { + A: u32, + B: []const u8, + C: void, + }; + fn doTheTest() void { + var y: u32 = 42; + const t0 = .{ .A = 123 }; + const t1 = .{ .B = "foo" }; + const t2 = .{ .C = {} }; + const t3 = .{ .A = y }; + const x0: U = t0; + var x1: U = t1; + const x2: U = t2; + var x3: U = t3; + expect(x0.A == 123); + expect(std.mem.eql(u8, x1.B, "foo")); + expect(x2 == .C); + expect(x3.A == y); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "cast from pointer to anonymous struct to pointer to union" { + const S = struct { + const U = union(enum) { + A: u32, + B: []const u8, + C: void, + }; + fn doTheTest() void { + var y: u32 = 42; + const t0 = &.{ .A = 123 }; + const t1 = &.{ .B = "foo" }; + const t2 = &.{ .C = {} }; + const t3 = &.{ .A = y }; + const x0: *const U = t0; + var x1: *const U = t1; + const x2: *const U = t2; + var x3: *const U = t3; + expect(x0.A == 123); + expect(std.mem.eql(u8, x1.B, "foo")); + expect(x2.* == .C); + expect(x3.A == y); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "method call on an empty union" { + const S = struct { + const MyUnion = union(MyUnionTag) { + pub const MyUnionTag = enum { X1, X2 }; + X1: [0]u8, + X2: [0]u8, + + pub fn useIt(self: *@This()) bool { + return true; + } + }; + + fn doTheTest() void { + var u = MyUnion{ .X1 = [0]u8{} }; + expect(u.useIt()); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "switching on non exhaustive union" { + const S = struct { + const E = enum(u8) { + a, + b, + _, + }; + const U = union(E) { + a: i32, + b: u32, + }; + fn doTheTest() void { + var a = U{ .a = 2 }; + switch (a) { + .a => |val| expect(val == 2), + .b => unreachable, + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "containers with single-field enums" { + const S = struct { + const A = union(enum) { f1 }; + const B = union(enum) { f1: void }; + const C = struct { a: A }; + const D = struct { a: B }; + + fn doTheTest() void { + var array1 = [1]A{A{ .f1 = {} }}; + var array2 = [1]B{B{ .f1 = {} }}; + expect(array1[0] == .f1); + expect(array2[0] == .f1); + + var struct1 = C{ .a = A{ .f1 = {} } }; + var struct2 = D{ .a = B{ .f1 = {} } }; + expect(struct1.a == .f1); + expect(struct2.a == .f1); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "@unionInit on union w/ tag but no fields" { + const S = struct { + const Type = enum(u8) { no_op = 105 }; + + const Data = union(Type) { + no_op: void, + + pub fn decode(buf: []const u8) Data { + return @unionInit(Data, "no_op", {}); + } + }; + + comptime { + expect(@sizeOf(Data) != 0); + } + + fn doTheTest() void { + var data: Data = .{ .no_op = .{} }; + var o = Data.decode(&[_]u8{}); + expectEqual(Type.no_op, o); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "union enum type gets a separate scope" { + const S = struct { + const U = union(enum) { + a: u8, + const foo = 1; + }; + + fn doTheTest() void { + expect(!@hasDecl(Tag(U), "foo")); + } + }; + + S.doTheTest(); +} diff --git a/test/behavior/usingnamespace.zig b/test/behavior/usingnamespace.zig new file mode 100644 index 0000000000..a44bf1bbc3 --- /dev/null +++ b/test/behavior/usingnamespace.zig @@ -0,0 +1,22 @@ +const std = @import("std"); + +fn Foo(comptime T: type) type { + return struct { + usingnamespace T; + }; +} + +test "usingnamespace inside a generic struct" { + const std2 = Foo(std); + const testing2 = Foo(std.testing); + std2.testing.expect(true); + testing2.expect(true); +} + +usingnamespace struct { + pub const foo = 42; +}; + +test "usingnamespace does not redeclare an imported variable" { + comptime std.testing.expect(foo == 42); +} diff --git a/test/behavior/var_args.zig b/test/behavior/var_args.zig new file mode 100644 index 0000000000..eae8f8f888 --- /dev/null +++ b/test/behavior/var_args.zig @@ -0,0 +1,83 @@ +const expect = @import("std").testing.expect; + +fn add(args: anytype) i32 { + var sum = @as(i32, 0); + { + comptime var i: usize = 0; + inline while (i < args.len) : (i += 1) { + sum += args[i]; + } + } + return sum; +} + +test "add arbitrary args" { + expect(add(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); + expect(add(.{@as(i32, 1234)}) == 1234); + expect(add(.{}) == 0); +} + +fn readFirstVarArg(args: anytype) void { + const value = args[0]; +} + +test "send void arg to var args" { + readFirstVarArg(.{{}}); +} + +test "pass args directly" { + expect(addSomeStuff(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); + expect(addSomeStuff(.{@as(i32, 1234)}) == 1234); + expect(addSomeStuff(.{}) == 0); +} + +fn addSomeStuff(args: anytype) i32 { + return add(args); +} + +test "runtime parameter before var args" { + expect(extraFn(10, .{}) == 0); + expect(extraFn(10, .{false}) == 1); + expect(extraFn(10, .{ false, true }) == 2); + + comptime { + expect(extraFn(10, .{}) == 0); + expect(extraFn(10, .{false}) == 1); + expect(extraFn(10, .{ false, true }) == 2); + } +} + +fn extraFn(extra: u32, args: anytype) usize { + if (args.len >= 1) { + expect(args[0] == false); + } + if (args.len >= 2) { + expect(args[1] == true); + } + return args.len; +} + +const foos = [_]fn (anytype) bool{ + foo1, + foo2, +}; + +fn foo1(args: anytype) bool { + return true; +} +fn foo2(args: anytype) bool { + return false; +} + +test "array of var args functions" { + expect(foos[0](.{})); + expect(!foos[1](.{})); +} + +test "pass zero length array to var args param" { + doNothingWithFirstArg(.{""}); +} + +fn doNothingWithFirstArg(args: anytype) void { + const a = args[0]; +} diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig new file mode 100644 index 0000000000..5035a824c7 --- /dev/null +++ b/test/behavior/vector.zig @@ -0,0 +1,655 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const mem = std.mem; +const math = std.math; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectApproxEqRel = std.testing.expectApproxEqRel; +const Vector = std.meta.Vector; + +test "implicit cast vector to array - bool" { + const S = struct { + fn doTheTest() void { + const a: Vector(4, bool) = [_]bool{ true, false, true, false }; + const result_array: [4]bool = a; + expect(mem.eql(bool, &result_array, &[4]bool{ true, false, true, false })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector wrap operators" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; + var x: Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 }; + expect(mem.eql(i32, &@as([4]i32, v +% x), &[4]i32{ -2147483648, 2147483645, 33, 44 })); + expect(mem.eql(i32, &@as([4]i32, v -% x), &[4]i32{ 2147483646, 2147483647, 27, 36 })); + expect(mem.eql(i32, &@as([4]i32, v *% x), &[4]i32{ 2147483647, 2, 90, 160 })); + var z: Vector(4, i32) = [4]i32{ 1, 2, 3, -2147483648 }; + expect(mem.eql(i32, &@as([4]i32, -%z), &[4]i32{ -1, -2, -3, -2147483648 })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector bin compares with mem.eql" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; + var x: Vector(4, i32) = [4]i32{ 1, 2147483647, 30, 4 }; + expect(mem.eql(bool, &@as([4]bool, v == x), &[4]bool{ false, false, true, false })); + expect(mem.eql(bool, &@as([4]bool, v != x), &[4]bool{ true, true, false, true })); + expect(mem.eql(bool, &@as([4]bool, v < x), &[4]bool{ false, true, false, false })); + expect(mem.eql(bool, &@as([4]bool, v > x), &[4]bool{ true, false, false, true })); + expect(mem.eql(bool, &@as([4]bool, v <= x), &[4]bool{ false, true, true, false })); + expect(mem.eql(bool, &@as([4]bool, v >= x), &[4]bool{ true, false, true, true })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector int operators" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [4]i32{ 10, 20, 30, 40 }; + var x: Vector(4, i32) = [4]i32{ 1, 2, 3, 4 }; + expect(mem.eql(i32, &@as([4]i32, v + x), &[4]i32{ 11, 22, 33, 44 })); + expect(mem.eql(i32, &@as([4]i32, v - x), &[4]i32{ 9, 18, 27, 36 })); + expect(mem.eql(i32, &@as([4]i32, v * x), &[4]i32{ 10, 40, 90, 160 })); + expect(mem.eql(i32, &@as([4]i32, -v), &[4]i32{ -10, -20, -30, -40 })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector float operators" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, f32) = [4]f32{ 10, 20, 30, 40 }; + var x: Vector(4, f32) = [4]f32{ 1, 2, 3, 4 }; + expect(mem.eql(f32, &@as([4]f32, v + x), &[4]f32{ 11, 22, 33, 44 })); + expect(mem.eql(f32, &@as([4]f32, v - x), &[4]f32{ 9, 18, 27, 36 })); + expect(mem.eql(f32, &@as([4]f32, v * x), &[4]f32{ 10, 40, 90, 160 })); + expect(mem.eql(f32, &@as([4]f32, -x), &[4]f32{ -1, -2, -3, -4 })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector bit operators" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 }; + var x: Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 }; + expect(mem.eql(u8, &@as([4]u8, v ^ x), &[4]u8{ 0b01011010, 0b10100101, 0b00000000, 0b11111111 })); + expect(mem.eql(u8, &@as([4]u8, v | x), &[4]u8{ 0b11111010, 0b10101111, 0b10101010, 0b11111111 })); + expect(mem.eql(u8, &@as([4]u8, v & x), &[4]u8{ 0b10100000, 0b00001010, 0b10101010, 0b00000000 })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "implicit cast vector to array" { + const S = struct { + fn doTheTest() void { + var a: Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; + var result_array: [4]i32 = a; + result_array = a; + expect(mem.eql(i32, &result_array, &[4]i32{ 1, 2, 3, 4 })); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "array to vector" { + var foo: f32 = 3.14; + var arr = [4]f32{ foo, 1.5, 0.0, 0.0 }; + var vec: Vector(4, f32) = arr; +} + +test "vector casts of sizes not divisable by 8" { + // https://github.com/ziglang/zig/issues/3563 + if (std.Target.current.os.tag == .dragonfly) return error.SkipZigTest; + + const S = struct { + fn doTheTest() void { + { + var v: Vector(4, u3) = [4]u3{ 5, 2, 3, 0 }; + var x: [4]u3 = v; + expect(mem.eql(u3, &x, &@as([4]u3, v))); + } + { + var v: Vector(4, u2) = [4]u2{ 1, 2, 3, 0 }; + var x: [4]u2 = v; + expect(mem.eql(u2, &x, &@as([4]u2, v))); + } + { + var v: Vector(4, u1) = [4]u1{ 1, 0, 1, 0 }; + var x: [4]u1 = v; + expect(mem.eql(u1, &x, &@as([4]u1, v))); + } + { + var v: Vector(4, bool) = [4]bool{ false, false, true, false }; + var x: [4]bool = v; + expect(mem.eql(bool, &x, &@as([4]bool, v))); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector @splat" { + const S = struct { + fn testForT(comptime N: comptime_int, v: anytype) void { + const T = @TypeOf(v); + var vec = @splat(N, v); + expectEqual(Vector(N, T), @TypeOf(vec)); + var as_array = @as([N]T, vec); + for (as_array) |elem| expectEqual(v, elem); + } + fn doTheTest() void { + // Splats with multiple-of-8 bit types that fill a 128bit vector. + testForT(16, @as(u8, 0xEE)); + testForT(8, @as(u16, 0xBEEF)); + testForT(4, @as(u32, 0xDEADBEEF)); + testForT(2, @as(u64, 0xCAFEF00DDEADBEEF)); + + testForT(8, @as(f16, 3.1415)); + testForT(4, @as(f32, 3.1415)); + testForT(2, @as(f64, 3.1415)); + + // Same but fill more than 128 bits. + testForT(16 * 2, @as(u8, 0xEE)); + testForT(8 * 2, @as(u16, 0xBEEF)); + testForT(4 * 2, @as(u32, 0xDEADBEEF)); + testForT(2 * 2, @as(u64, 0xCAFEF00DDEADBEEF)); + + testForT(8 * 2, @as(f16, 3.1415)); + testForT(4 * 2, @as(f32, 3.1415)); + testForT(2 * 2, @as(f64, 3.1415)); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "load vector elements via comptime index" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [_]i32{ 1, 2, 3, undefined }; + expect(v[0] == 1); + expect(v[1] == 2); + expect(loadv(&v[2]) == 3); + } + fn loadv(ptr: anytype) i32 { + return ptr.*; + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "store vector elements via comptime index" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [_]i32{ 1, 5, 3, undefined }; + + v[2] = 42; + expect(v[1] == 5); + v[3] = -364; + expect(v[2] == 42); + expect(-364 == v[3]); + + storev(&v[0], 100); + expect(v[0] == 100); + } + fn storev(ptr: anytype, x: i32) void { + ptr.* = x; + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "load vector elements via runtime index" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [_]i32{ 1, 2, 3, undefined }; + var i: u32 = 0; + expect(v[i] == 1); + i += 1; + expect(v[i] == 2); + i += 1; + expect(v[i] == 3); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "store vector elements via runtime index" { + const S = struct { + fn doTheTest() void { + var v: Vector(4, i32) = [_]i32{ 1, 5, 3, undefined }; + var i: u32 = 2; + v[i] = 1; + expect(v[1] == 5); + expect(v[2] == 1); + i += 1; + v[i] = -364; + expect(-364 == v[3]); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "initialize vector which is a struct field" { + const Vec4Obj = struct { + data: Vector(4, f32), + }; + + const S = struct { + fn doTheTest() void { + var foo = Vec4Obj{ + .data = [_]f32{ 1, 2, 3, 4 }, + }; + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector comparison operators" { + const S = struct { + fn doTheTest() void { + { + const v1: Vector(4, bool) = [_]bool{ true, false, true, false }; + const v2: Vector(4, bool) = [_]bool{ false, true, false, true }; + expectEqual(@splat(4, true), v1 == v1); + expectEqual(@splat(4, false), v1 == v2); + expectEqual(@splat(4, true), v1 != v2); + expectEqual(@splat(4, false), v2 != v2); + } + { + const v1 = @splat(4, @as(u32, 0xc0ffeeee)); + const v2: Vector(4, c_uint) = v1; + const v3 = @splat(4, @as(u32, 0xdeadbeef)); + expectEqual(@splat(4, true), v1 == v2); + expectEqual(@splat(4, false), v1 == v3); + expectEqual(@splat(4, true), v1 != v3); + expectEqual(@splat(4, false), v1 != v2); + } + { + // Comptime-known LHS/RHS + var v1: @Vector(4, u32) = [_]u32{ 2, 1, 2, 1 }; + const v2 = @splat(4, @as(u32, 2)); + const v3: @Vector(4, bool) = [_]bool{ true, false, true, false }; + expectEqual(v3, v1 == v2); + expectEqual(v3, v2 == v1); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector division operators" { + const S = struct { + fn doTheTestDiv(comptime T: type, x: Vector(4, T), y: Vector(4, T)) void { + if (!comptime std.meta.trait.isSignedInt(T)) { + const d0 = x / y; + for (@as([4]T, d0)) |v, i| { + expectEqual(x[i] / y[i], v); + } + } + const d1 = @divExact(x, y); + for (@as([4]T, d1)) |v, i| { + expectEqual(@divExact(x[i], y[i]), v); + } + const d2 = @divFloor(x, y); + for (@as([4]T, d2)) |v, i| { + expectEqual(@divFloor(x[i], y[i]), v); + } + const d3 = @divTrunc(x, y); + for (@as([4]T, d3)) |v, i| { + expectEqual(@divTrunc(x[i], y[i]), v); + } + } + + fn doTheTestMod(comptime T: type, x: Vector(4, T), y: Vector(4, T)) void { + if ((!comptime std.meta.trait.isSignedInt(T)) and @typeInfo(T) != .Float) { + const r0 = x % y; + for (@as([4]T, r0)) |v, i| { + expectEqual(x[i] % y[i], v); + } + } + const r1 = @mod(x, y); + for (@as([4]T, r1)) |v, i| { + expectEqual(@mod(x[i], y[i]), v); + } + const r2 = @rem(x, y); + for (@as([4]T, r2)) |v, i| { + expectEqual(@rem(x[i], y[i]), v); + } + } + + fn doTheTest() void { + // https://github.com/ziglang/zig/issues/4952 + if (builtin.target.os.tag != .windows) { + doTheTestDiv(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, -1.0, -2.0 }); + } + + doTheTestDiv(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, -1.0, -2.0 }); + doTheTestDiv(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, -1.0, -2.0 }); + + // https://github.com/ziglang/zig/issues/4952 + if (builtin.target.os.tag != .windows) { + doTheTestMod(f16, [4]f16{ 4.0, -4.0, 4.0, -4.0 }, [4]f16{ 1.0, 2.0, 0.5, 3.0 }); + } + doTheTestMod(f32, [4]f32{ 4.0, -4.0, 4.0, -4.0 }, [4]f32{ 1.0, 2.0, 0.5, 3.0 }); + doTheTestMod(f64, [4]f64{ 4.0, -4.0, 4.0, -4.0 }, [4]f64{ 1.0, 2.0, 0.5, 3.0 }); + + doTheTestDiv(i8, [4]i8{ 4, -4, 4, -4 }, [4]i8{ 1, 2, -1, -2 }); + doTheTestDiv(i16, [4]i16{ 4, -4, 4, -4 }, [4]i16{ 1, 2, -1, -2 }); + doTheTestDiv(i32, [4]i32{ 4, -4, 4, -4 }, [4]i32{ 1, 2, -1, -2 }); + doTheTestDiv(i64, [4]i64{ 4, -4, 4, -4 }, [4]i64{ 1, 2, -1, -2 }); + + doTheTestMod(i8, [4]i8{ 4, -4, 4, -4 }, [4]i8{ 1, 2, 4, 8 }); + doTheTestMod(i16, [4]i16{ 4, -4, 4, -4 }, [4]i16{ 1, 2, 4, 8 }); + doTheTestMod(i32, [4]i32{ 4, -4, 4, -4 }, [4]i32{ 1, 2, 4, 8 }); + doTheTestMod(i64, [4]i64{ 4, -4, 4, -4 }, [4]i64{ 1, 2, 4, 8 }); + + doTheTestDiv(u8, [4]u8{ 1, 2, 4, 8 }, [4]u8{ 1, 1, 2, 4 }); + doTheTestDiv(u16, [4]u16{ 1, 2, 4, 8 }, [4]u16{ 1, 1, 2, 4 }); + doTheTestDiv(u32, [4]u32{ 1, 2, 4, 8 }, [4]u32{ 1, 1, 2, 4 }); + doTheTestDiv(u64, [4]u64{ 1, 2, 4, 8 }, [4]u64{ 1, 1, 2, 4 }); + + doTheTestMod(u8, [4]u8{ 1, 2, 4, 8 }, [4]u8{ 1, 1, 2, 4 }); + doTheTestMod(u16, [4]u16{ 1, 2, 4, 8 }, [4]u16{ 1, 1, 2, 4 }); + doTheTestMod(u32, [4]u32{ 1, 2, 4, 8 }, [4]u32{ 1, 1, 2, 4 }); + doTheTestMod(u64, [4]u64{ 1, 2, 4, 8 }, [4]u64{ 1, 1, 2, 4 }); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector bitwise not operator" { + const S = struct { + fn doTheTestNot(comptime T: type, x: Vector(4, T)) void { + var y = ~x; + for (@as([4]T, y)) |v, i| { + expectEqual(~x[i], v); + } + } + fn doTheTest() void { + doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 }); + doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 }); + doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 }); + doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 }); + + doTheTestNot(u8, [_]u8{ 0, 2, 4, 255 }); + doTheTestNot(u16, [_]u16{ 0, 2, 4, 255 }); + doTheTestNot(u32, [_]u32{ 0, 2, 4, 255 }); + doTheTestNot(u64, [_]u64{ 0, 2, 4, 255 }); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector shift operators" { + // TODO investigate why this fails when cross-compiled to wasm. + if (builtin.target.os.tag == .wasi) return error.SkipZigTest; + + const S = struct { + fn doTheTestShift(x: anytype, y: anytype) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + const TY = @typeInfo(@TypeOf(y)).Array.child; + + var xv = @as(Vector(N, TX), x); + var yv = @as(Vector(N, TY), y); + + var z0 = xv >> yv; + for (@as([N]TX, z0)) |v, i| { + expectEqual(x[i] >> y[i], v); + } + var z1 = xv << yv; + for (@as([N]TX, z1)) |v, i| { + expectEqual(x[i] << y[i], v); + } + } + fn doTheTestShiftExact(x: anytype, y: anytype, dir: enum { Left, Right }) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + const TY = @typeInfo(@TypeOf(y)).Array.child; + + var xv = @as(Vector(N, TX), x); + var yv = @as(Vector(N, TY), y); + + var z = if (dir == .Left) @shlExact(xv, yv) else @shrExact(xv, yv); + for (@as([N]TX, z)) |v, i| { + const check = if (dir == .Left) x[i] << y[i] else x[i] >> y[i]; + expectEqual(check, v); + } + } + fn doTheTest() void { + doTheTestShift([_]u8{ 0, 2, 4, math.maxInt(u8) }, [_]u3{ 2, 0, 2, 7 }); + doTheTestShift([_]u16{ 0, 2, 4, math.maxInt(u16) }, [_]u4{ 2, 0, 2, 15 }); + doTheTestShift([_]u24{ 0, 2, 4, math.maxInt(u24) }, [_]u5{ 2, 0, 2, 23 }); + doTheTestShift([_]u32{ 0, 2, 4, math.maxInt(u32) }, [_]u5{ 2, 0, 2, 31 }); + doTheTestShift([_]u64{ 0xfe, math.maxInt(u64) }, [_]u6{ 0, 63 }); + + doTheTestShift([_]i8{ 0, 2, 4, math.maxInt(i8) }, [_]u3{ 2, 0, 2, 7 }); + doTheTestShift([_]i16{ 0, 2, 4, math.maxInt(i16) }, [_]u4{ 2, 0, 2, 7 }); + doTheTestShift([_]i24{ 0, 2, 4, math.maxInt(i24) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i32{ 0, 2, 4, math.maxInt(i32) }, [_]u5{ 2, 0, 2, 7 }); + doTheTestShift([_]i64{ 0xfe, math.maxInt(i64) }, [_]u6{ 0, 63 }); + + doTheTestShiftExact([_]u8{ 0, 1, 1 << 7, math.maxInt(u8) ^ 1 }, [_]u3{ 4, 0, 7, 1 }, .Right); + doTheTestShiftExact([_]u16{ 0, 1, 1 << 15, math.maxInt(u16) ^ 1 }, [_]u4{ 4, 0, 15, 1 }, .Right); + doTheTestShiftExact([_]u24{ 0, 1, 1 << 23, math.maxInt(u24) ^ 1 }, [_]u5{ 4, 0, 23, 1 }, .Right); + doTheTestShiftExact([_]u32{ 0, 1, 1 << 31, math.maxInt(u32) ^ 1 }, [_]u5{ 4, 0, 31, 1 }, .Right); + doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 63, 0 }, .Right); + + doTheTestShiftExact([_]u8{ 0, 1, 1, math.maxInt(u8) ^ (1 << 7) }, [_]u3{ 4, 0, 7, 1 }, .Left); + doTheTestShiftExact([_]u16{ 0, 1, 1, math.maxInt(u16) ^ (1 << 15) }, [_]u4{ 4, 0, 15, 1 }, .Left); + doTheTestShiftExact([_]u24{ 0, 1, 1, math.maxInt(u24) ^ (1 << 23) }, [_]u5{ 4, 0, 23, 1 }, .Left); + doTheTestShiftExact([_]u32{ 0, 1, 1, math.maxInt(u32) ^ (1 << 31) }, [_]u5{ 4, 0, 31, 1 }, .Left); + doTheTestShiftExact([_]u64{ 1 << 63, 1 }, [_]u6{ 0, 63 }, .Left); + } + }; + + switch (builtin.target.cpu.arch) { + .i386, + .aarch64, + .aarch64_be, + .aarch64_32, + .arm, + .armeb, + .thumb, + .thumbeb, + .mips, + .mipsel, + .mips64, + .mips64el, + .riscv64, + .sparcv9, + => { + // LLVM miscompiles on this architecture + // https://github.com/ziglang/zig/issues/4951 + return error.SkipZigTest; + }, + else => {}, + } + + S.doTheTest(); + comptime S.doTheTest(); +} + +test "vector reduce operation" { + const S = struct { + fn doTheTestReduce(comptime op: std.builtin.ReduceOp, x: anytype, expected: anytype) void { + const N = @typeInfo(@TypeOf(x)).Array.len; + const TX = @typeInfo(@TypeOf(x)).Array.child; + + // wasmtime: unknown import: `env::fminf` has not been defined + // https://github.com/ziglang/zig/issues/8131 + switch (builtin.target.cpu.arch) { + .wasm32 => switch (@typeInfo(TX)) { + .Float => switch (op) { + .Min, + .Max, + => return, + else => {}, + }, + else => {}, + }, + else => {}, + } + + var r = @reduce(op, @as(Vector(N, TX), x)); + switch (@typeInfo(TX)) { + .Int, .Bool => expectEqual(expected, r), + .Float => { + const expected_nan = math.isNan(expected); + const got_nan = math.isNan(r); + + if (expected_nan and got_nan) { + // Do this check explicitly as two NaN values are never + // equal. + } else { + expectApproxEqRel(expected, r, math.sqrt(math.epsilon(TX))); + } + }, + else => unreachable, + } + } + fn doTheTest() void { + doTheTestReduce(.Add, [4]i16{ -9, -99, -999, -9999 }, @as(i32, -11106)); + doTheTestReduce(.Add, [4]u16{ 9, 99, 999, 9999 }, @as(u32, 11106)); + doTheTestReduce(.Add, [4]i32{ -9, -99, -999, -9999 }, @as(i32, -11106)); + doTheTestReduce(.Add, [4]u32{ 9, 99, 999, 9999 }, @as(u32, 11106)); + doTheTestReduce(.Add, [4]i64{ -9, -99, -999, -9999 }, @as(i64, -11106)); + doTheTestReduce(.Add, [4]u64{ 9, 99, 999, 9999 }, @as(u64, 11106)); + doTheTestReduce(.Add, [4]i128{ -9, -99, -999, -9999 }, @as(i128, -11106)); + doTheTestReduce(.Add, [4]u128{ 9, 99, 999, 9999 }, @as(u128, 11106)); + doTheTestReduce(.Add, [4]f16{ -1.9, 5.1, -60.3, 100.0 }, @as(f16, 42.9)); + doTheTestReduce(.Add, [4]f32{ -1.9, 5.1, -60.3, 100.0 }, @as(f32, 42.9)); + doTheTestReduce(.Add, [4]f64{ -1.9, 5.1, -60.3, 100.0 }, @as(f64, 42.9)); + + doTheTestReduce(.And, [4]bool{ true, false, true, true }, @as(bool, false)); + doTheTestReduce(.And, [4]u1{ 1, 0, 1, 1 }, @as(u1, 0)); + doTheTestReduce(.And, [4]u16{ 0xffff, 0xff55, 0xaaff, 0x1010 }, @as(u16, 0x10)); + doTheTestReduce(.And, [4]u32{ 0xffffffff, 0xffff5555, 0xaaaaffff, 0x10101010 }, @as(u32, 0x1010)); + doTheTestReduce(.And, [4]u64{ 0xffffffff, 0xffff5555, 0xaaaaffff, 0x10101010 }, @as(u64, 0x1010)); + + doTheTestReduce(.Min, [4]i16{ -1, 2, 3, 4 }, @as(i16, -1)); + doTheTestReduce(.Min, [4]u16{ 1, 2, 3, 4 }, @as(u16, 1)); + doTheTestReduce(.Min, [4]i32{ 1234567, -386, 0, 3 }, @as(i32, -386)); + doTheTestReduce(.Min, [4]u32{ 99, 9999, 9, 99999 }, @as(u32, 9)); + + // LLVM 11 ERROR: Cannot select type + // https://github.com/ziglang/zig/issues/7138 + if (builtin.target.cpu.arch != .aarch64) { + doTheTestReduce(.Min, [4]i64{ 1234567, -386, 0, 3 }, @as(i64, -386)); + doTheTestReduce(.Min, [4]u64{ 99, 9999, 9, 99999 }, @as(u64, 9)); + } + + doTheTestReduce(.Min, [4]i128{ 1234567, -386, 0, 3 }, @as(i128, -386)); + doTheTestReduce(.Min, [4]u128{ 99, 9999, 9, 99999 }, @as(u128, 9)); + doTheTestReduce(.Min, [4]f16{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f16, -100.0)); + doTheTestReduce(.Min, [4]f32{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f32, -100.0)); + doTheTestReduce(.Min, [4]f64{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f64, -100.0)); + + doTheTestReduce(.Max, [4]i16{ -1, 2, 3, 4 }, @as(i16, 4)); + doTheTestReduce(.Max, [4]u16{ 1, 2, 3, 4 }, @as(u16, 4)); + doTheTestReduce(.Max, [4]i32{ 1234567, -386, 0, 3 }, @as(i32, 1234567)); + doTheTestReduce(.Max, [4]u32{ 99, 9999, 9, 99999 }, @as(u32, 99999)); + + // LLVM 11 ERROR: Cannot select type + // https://github.com/ziglang/zig/issues/7138 + if (builtin.target.cpu.arch != .aarch64) { + doTheTestReduce(.Max, [4]i64{ 1234567, -386, 0, 3 }, @as(i64, 1234567)); + doTheTestReduce(.Max, [4]u64{ 99, 9999, 9, 99999 }, @as(u64, 99999)); + } + + doTheTestReduce(.Max, [4]i128{ 1234567, -386, 0, 3 }, @as(i128, 1234567)); + doTheTestReduce(.Max, [4]u128{ 99, 9999, 9, 99999 }, @as(u128, 99999)); + doTheTestReduce(.Max, [4]f16{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f16, 10.0e9)); + doTheTestReduce(.Max, [4]f32{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f32, 10.0e9)); + doTheTestReduce(.Max, [4]f64{ -10.3, 10.0e9, 13.0, -100.0 }, @as(f64, 10.0e9)); + + doTheTestReduce(.Mul, [4]i16{ -1, 2, 3, 4 }, @as(i16, -24)); + doTheTestReduce(.Mul, [4]u16{ 1, 2, 3, 4 }, @as(u16, 24)); + doTheTestReduce(.Mul, [4]i32{ -9, -99, -999, 999 }, @as(i32, -889218891)); + doTheTestReduce(.Mul, [4]u32{ 1, 2, 3, 4 }, @as(u32, 24)); + doTheTestReduce(.Mul, [4]i64{ 9, 99, 999, 9999 }, @as(i64, 8900199891)); + doTheTestReduce(.Mul, [4]u64{ 9, 99, 999, 9999 }, @as(u64, 8900199891)); + doTheTestReduce(.Mul, [4]i128{ -9, -99, -999, 9999 }, @as(i128, -8900199891)); + doTheTestReduce(.Mul, [4]u128{ 9, 99, 999, 9999 }, @as(u128, 8900199891)); + doTheTestReduce(.Mul, [4]f16{ -1.9, 5.1, -60.3, 100.0 }, @as(f16, 58430.7)); + doTheTestReduce(.Mul, [4]f32{ -1.9, 5.1, -60.3, 100.0 }, @as(f32, 58430.7)); + doTheTestReduce(.Mul, [4]f64{ -1.9, 5.1, -60.3, 100.0 }, @as(f64, 58430.7)); + + doTheTestReduce(.Or, [4]bool{ false, true, false, false }, @as(bool, true)); + doTheTestReduce(.Or, [4]u1{ 0, 1, 0, 0 }, @as(u1, 1)); + doTheTestReduce(.Or, [4]u16{ 0xff00, 0xff00, 0xf0, 0xf }, ~@as(u16, 0)); + doTheTestReduce(.Or, [4]u32{ 0xffff0000, 0xff00, 0xf0, 0xf }, ~@as(u32, 0)); + doTheTestReduce(.Or, [4]u64{ 0xffff0000, 0xff00, 0xf0, 0xf }, @as(u64, 0xffffffff)); + doTheTestReduce(.Or, [4]u128{ 0xffff0000, 0xff00, 0xf0, 0xf }, @as(u128, 0xffffffff)); + + doTheTestReduce(.Xor, [4]bool{ true, true, true, false }, @as(bool, true)); + doTheTestReduce(.Xor, [4]u1{ 1, 1, 1, 0 }, @as(u1, 1)); + doTheTestReduce(.Xor, [4]u16{ 0x0000, 0x3333, 0x8888, 0x4444 }, ~@as(u16, 0)); + doTheTestReduce(.Xor, [4]u32{ 0x00000000, 0x33333333, 0x88888888, 0x44444444 }, ~@as(u32, 0)); + doTheTestReduce(.Xor, [4]u64{ 0x00000000, 0x33333333, 0x88888888, 0x44444444 }, @as(u64, 0xffffffff)); + doTheTestReduce(.Xor, [4]u128{ 0x00000000, 0x33333333, 0x88888888, 0x44444444 }, @as(u128, 0xffffffff)); + + // Test the reduction on vectors containing NaNs. + const f16_nan = math.nan(f16); + const f32_nan = math.nan(f32); + const f64_nan = math.nan(f64); + + doTheTestReduce(.Add, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); + doTheTestReduce(.Add, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); + doTheTestReduce(.Add, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + + // LLVM 11 ERROR: Cannot select type + // https://github.com/ziglang/zig/issues/7138 + if (false) { + doTheTestReduce(.Min, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); + doTheTestReduce(.Min, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); + doTheTestReduce(.Min, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + + doTheTestReduce(.Max, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); + doTheTestReduce(.Max, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); + doTheTestReduce(.Max, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + } + + doTheTestReduce(.Mul, [4]f16{ -1.9, 5.1, f16_nan, 100.0 }, f16_nan); + doTheTestReduce(.Mul, [4]f32{ -1.9, 5.1, f32_nan, 100.0 }, f32_nan); + doTheTestReduce(.Mul, [4]f64{ -1.9, 5.1, f64_nan, 100.0 }, f64_nan); + } + }; + + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/void.zig b/test/behavior/void.zig new file mode 100644 index 0000000000..80df9fe4f9 --- /dev/null +++ b/test/behavior/void.zig @@ -0,0 +1,40 @@ +const expect = @import("std").testing.expect; + +const Foo = struct { + a: void, + b: i32, + c: void, +}; + +test "compare void with void compile time known" { + comptime { + const foo = Foo{ + .a = {}, + .b = 1, + .c = {}, + }; + expect(foo.a == {}); + } +} + +test "iterate over a void slice" { + var j: usize = 0; + for (times(10)) |_, i| { + expect(i == j); + j += 1; + } +} + +fn times(n: usize) []const void { + return @as([*]void, undefined)[0..n]; +} + +test "void optional" { + var x: ?void = {}; + expect(x != null); +} + +test "void array as a local variable initializer" { + var x = [_]void{{}} ** 1004; + var y = x[0]; +} diff --git a/test/behavior/wasm.zig b/test/behavior/wasm.zig new file mode 100644 index 0000000000..24557ee19b --- /dev/null +++ b/test/behavior/wasm.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "memory size and grow" { + var prev = @wasmMemorySize(0); + expect(prev == @wasmMemoryGrow(0, 1)); + expect(prev + 1 == @wasmMemorySize(0)); +} diff --git a/test/behavior/while.zig b/test/behavior/while.zig new file mode 100644 index 0000000000..c9207396f7 --- /dev/null +++ b/test/behavior/while.zig @@ -0,0 +1,289 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "while loop" { + var i: i32 = 0; + while (i < 4) { + i += 1; + } + expect(i == 4); + expect(whileLoop1() == 1); +} +fn whileLoop1() i32 { + return whileLoop2(); +} +fn whileLoop2() i32 { + while (true) { + return 1; + } +} + +test "static eval while" { + expect(static_eval_while_number == 1); +} +const static_eval_while_number = staticWhileLoop1(); +fn staticWhileLoop1() i32 { + return whileLoop2(); +} +fn staticWhileLoop2() i32 { + while (true) { + return 1; + } +} + +test "continue and break" { + runContinueAndBreakTest(); + 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; + } + 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; + { + var i: i32 = 0; + while (i < 10) : (i += 1) { + if (i == 5) continue; + sum += i; + } + } + expect(sum == 40); +} + +test "while with else" { + var sum: i32 = 0; + var i: i32 = 0; + var got_else: i32 = 0; + while (i < 10) : (i += 1) { + sum += 1; + } else { + got_else += 1; + } + expect(sum == 10); + expect(got_else == 1); +} + +test "while with optional as condition" { + numbers_left = 10; + var sum: i32 = 0; + while (getNumberOrNull()) |value| { + sum += value; + } + 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; + expect(got_else == 0); + } else { + got_else += 1; + } + expect(sum == 45); + 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| { + expect(err == error.OutOfNumbers); + got_else += 1; + } + expect(sum == 45); + 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); + 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); + expect(result == 10); +} + +test "while on error union with else result follow else prong" { + const result = while (returnError()) |value| { + break value; + } else |err| + @as(i32, 2); + expect(result == 2); +} + +test "while on error union with else result follow break prong" { + const result = while (returnSuccess(10)) |value| { + break value; + } else |err| + @as(i32, 2); + 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); + 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); + 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(); +} + +fn testContinueOuter() void { + var i: usize = 0; + outer: while (i < 10) : (i += 1) { + while (true) { + continue :outer; + } + } +} + +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; + expect(ok); + } + }; + S.entry(true, false); + comptime 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; + expect(ok); + } + }; + S.entry(true, false); + comptime 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; + expect(ok); + } + }; + S.entry(true, false); + comptime S.entry(true, false); +} + +test "while copies its payload" { + const S = struct { + fn doTheTest() void { + var tmp: ?i32 = 10; + while (tmp) |value| { + // Modify the original variable + tmp = null; + expect(value == 10); + } + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig new file mode 100644 index 0000000000..785a1729dc --- /dev/null +++ b/test/behavior/widening.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; + +test "integer widening" { + var a: u8 = 250; + var b: u16 = a; + var c: u32 = b; + var d: u64 = c; + var e: u64 = d; + var f: u128 = e; + expect(f == a); +} + +test "implicit unsigned integer to signed integer" { + var a: u8 = 250; + var b: i16 = a; + expect(b == 250); +} + +test "float widening" { + var a: f16 = 12.34; + var b: f32 = a; + var c: f64 = b; + var d: f128 = c; + expect(a == b); + expect(b == c); + expect(c == d); +} + +test "float widening f16 to f128" { + // TODO https://github.com/ziglang/zig/issues/3282 + if (@import("builtin").target.cpu.arch == .aarch64) return error.SkipZigTest; + if (@import("builtin").target.cpu.arch == .powerpc64le) return error.SkipZigTest; + + var x: f16 = 12.34; + var y: f128 = x; + expect(x == y); +} |
