stage2: LLVM backend: implement wrap_optional AIR

and move over some passing tests
This commit is contained in:
Andrew Kelley 2021-10-14 22:16:26 -07:00
parent 55eea3b045
commit cacd5366a6
11 changed files with 428 additions and 402 deletions

View File

@ -2341,10 +2341,33 @@ pub const FuncGen = struct {
}
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
if (self.liveness.isUnused(inst)) return null;
return self.todo("implement llvm codegen for 'airWrapOptional'", .{});
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand_ty = self.air.typeOf(ty_op.operand);
const non_null_bit = self.context.intType(1).constAllOnes();
if (!operand_ty.hasCodeGenBits()) return non_null_bit;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOfIndex(inst);
if (optional_ty.isPtrLikeOptional()) return operand;
const llvm_optional_ty = try self.dg.llvmType(optional_ty);
if (isByRef(optional_ty)) {
const optional_ptr = self.buildAlloca(llvm_optional_ty);
const payload_ptr = self.builder.buildStructGEP(optional_ptr, 0, "");
var buf: Type.Payload.ElemType = undefined;
const payload_ty = operand_ty.optionalChild(&buf);
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = payload_ty,
};
const payload_ptr_ty = Type.initPayload(&ptr_ty_payload.base);
self.store(payload_ptr, payload_ptr_ty, operand, .NotAtomic);
const non_null_ptr = self.builder.buildStructGEP(optional_ptr, 1, "");
_ = self.builder.buildStore(non_null_bit, non_null_ptr);
return optional_ptr;
}
const partial = self.builder.buildInsertValue(llvm_optional_ty.getUndef(), operand, 0, "");
return self.builder.buildInsertValue(partial, non_null_bit, 1, "");
}
fn airWrapErrUnionPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {

View File

@ -28,6 +28,7 @@ test {
_ = @import("behavior/member_func.zig");
_ = @import("behavior/optional.zig");
_ = @import("behavior/pointers.zig");
_ = @import("behavior/pub_enum.zig");
_ = @import("behavior/slice.zig");
_ = @import("behavior/sizeof_and_typeof.zig");
_ = @import("behavior/struct.zig");
@ -140,7 +141,6 @@ test {
_ = @import("behavior/pointers_stage1.zig");
_ = @import("behavior/popcount.zig");
_ = @import("behavior/ptrcast.zig");
_ = @import("behavior/pub_enum.zig");
_ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
_ = @import("behavior/reflection.zig");
{

View File

@ -411,3 +411,38 @@ test "use of declaration with same name as primitive" {
const c: @"u8" = 300;
try expect(c == 300);
}
fn emptyFn() void {}
test "constant equal function pointers" {
const alias = emptyFn;
try expect(comptime x: {
break :x emptyFn == alias;
});
}
test "multiline string literal is null terminated" {
const s1 =
\\one
\\two)
\\three
;
const s2 = "one\ntwo)\nthree";
try expect(std.cstr.cmp(s1, s2) == 0);
}
test "self reference through fn ptr field" {
const S = struct {
const A = struct {
f: fn (A) u8,
};
fn foo(a: A) u8 {
_ = a;
return 12;
}
};
var a: S.A = undefined;
a.f = S.foo;
try expect(a.f(a) == 12);
}

View File

@ -217,3 +217,154 @@ const vertices = [_]Vertex{
.b = 1.0,
},
};
test "statically initialized list" {
try expect(static_point_list[0].x == 1);
try expect(static_point_list[0].y == 2);
try expect(static_point_list[1].x == 3);
try 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 "statically initialized array literal" {
const y: [4]u8 = st_init_arr_lit_x;
try expect(y[3] == 4);
}
const st_init_arr_lit_x = [_]u8{ 1, 2, 3, 4 };
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" {
try expect(performFn('t', 1) == 6);
try expect(performFn('o', 0) == 1);
try expect(performFn('w', 99) == 99);
}
test "create global array with for loop" {
try expect(global_array[5] == 5 * 5);
try 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;
};
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" {
try expect(doesAlotT(u32, 2) == 2);
}
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;
try expect(diamond_info.version == 0);
try expect(res.version == 1);
}
}
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 {
_ = T;
}
test "zero extend from u0 to u1" {
var zero_u0: u0 = 0;
var zero_u1: u1 = zero_u0;
try expect(zero_u1 == 0);
}
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");
}
}
}

View File

@ -2,27 +2,6 @@ const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
test "statically initialized list" {
try expect(static_point_list[0].x == 1);
try expect(static_point_list[0].y == 2);
try expect(static_point_list[1].x == 3);
try 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" {
try expect(static_vec3.data[2] == 1.0);
try expect(vec3(0.0, 0.0, 3.0).data[2] == 3.0);
@ -54,27 +33,6 @@ var st_init_str_foo = StInitStrFoo{
.y = true,
};
test "statically initialized array literal" {
const y: [4]u8 = st_init_arr_lit_x;
try expect(y[3] == 4);
}
const st_init_arr_lit_x = [_]u8{
1,
2,
3,
4,
};
test "const slice" {
comptime {
const a = "1234567890";
try expect(a.len == 10);
const b = a[1..2];
try expect(b.len == 1);
try expect(b[0] == '2');
}
}
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;
@ -87,52 +45,6 @@ test "inlined loop has array literal with elided runtime scope on first iteratio
}
}
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" {
try expect(performFn('t', 1) == 6);
try expect(performFn('o', 0) == 1);
try expect(performFn('w', 99) == 99);
}
test "eval @setFloatMode at compile-time" {
const result = comptime fnWithFloatMode();
try expect(result == 1234.0);
@ -204,19 +116,6 @@ const Foo = struct {
var foo_contents = Foo{ .name = "a" };
const foo_ref = &foo_contents;
test "create global array with for loop" {
try expect(global_array[5] == 5 * 5);
try 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;
};
const hi1 = "hi";
const hi2 = hi1;
test "const global shares pointer with other same one" {
@ -262,13 +161,6 @@ test "string literal used as comptime slice is memoized" {
comptime try expect(TypeWithCompTimeSlice("link").Node == TypeWithCompTimeSlice("link").Node);
}
test "comptime slice of undefined pointer of length 0" {
const slice1 = @as([*]i32, undefined)[0..0];
try expect(slice1.len == 0);
const slice2 = @as([*]i32, undefined)[100..100];
try expect(slice2.len == 0);
}
fn copyWithPartialInline(s: []u32, b: []u8) void {
comptime var i: usize = 0;
inline while (i < 4) : (i += 1) {
@ -308,44 +200,6 @@ 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" {
try expect(doesAlotT(u32, 2) == 2);
}
test "comptime slice of slice preserves comptime var" {
comptime {
var buff: [10]u8 = undefined;
buff[0..][0..][0] = 1;
try 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;
try expect(buff[0..][0..][0] == 1);
}
}
const SingleFieldStruct = struct {
x: i32,
@ -362,15 +216,6 @@ test "const ptr to comptime mutable data is not memoized" {
}
}
test "array concat of slices gives slice" {
comptime {
var a: []const u8 = "aoeu";
var b: []const u8 = "asdf";
const c = a ++ b;
try expect(std.mem.eql(u8, c, "aoeuasdf"));
}
}
test "comptime shlWithOverflow" {
const ct_shifted: u64 = comptime amt: {
var amt = @as(u64, 0);
@ -401,43 +246,6 @@ test "runtime 128 bit integer division" {
try 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;
try expect(diamond_info.version == 0);
try expect(res.version == 1);
}
}
test "slice of type" {
comptime {
var types_array = [_]type{ i32, f64, type };
for (types_array) |T, i| {
switch (i) {
0 => try expect(T == i32),
1 => try expect(T == f64),
2 => try expect(T == type),
else => unreachable,
}
}
for (types_array[0..]) |T, i| {
switch (i) {
0 => try expect(T == i32),
1 => try expect(T == f64),
2 => try expect(T == type),
else => unreachable,
}
}
}
}
const Wrapper = struct {
T: type,
};
@ -502,55 +310,12 @@ test "inline for with same type but different values" {
try 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 {
_ = T;
}
test "zero extend from u0 to u1" {
var zero_u0: u0 = 0;
var zero_u1: u1 = zero_u0;
try expect(zero_u1 == 0);
}
test "bit shift a u1" {
var x: u1 = 1;
var y = x << 0;
try 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];
try expect(sliceA[1] == 2);
try 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;
try expect(str.len == 1);
try expect(std.mem.eql(u8, str, "1"));
const str2 = bs ++ "";
try expect(str2.len == 1);
try expect(std.mem.eql(u8, str2, "1"));
}
test "comptime bitwise operators" {
comptime {
try expect(3 & 1 == 1);
@ -602,19 +367,6 @@ test "comptime assign int to optional int" {
}
}
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 {

View File

@ -7,13 +7,6 @@ const builtin = @import("builtin");
fn emptyFn() void {}
test "constant equal function pointers" {
const alias = emptyFn;
try expect(comptime x: {
break :x emptyFn == alias;
});
}
const addr1 = @ptrCast(*const u8, emptyFn);
test "comptime cast fn to ptr" {
const addr2 = @ptrCast(*const u8, emptyFn);
@ -35,17 +28,7 @@ test "string escapes" {
try expectEqualStrings("\u{1234}\u{069}\u{1}", "\xe1\x88\xb4\x69\x01");
}
test "multiline string literal is null terminated" {
const s1 =
\\one
\\two)
\\three
;
const s2 = "one\ntwo)\nthree";
try expect(std.cstr.cmp(s1, s2) == 0);
}
test "explicit cast maybe pointers" {
test "explicit cast optional pointers" {
const a: ?*i32 = undefined;
const b: ?*f32 = @ptrCast(?*f32, a);
_ = b;
@ -159,22 +142,6 @@ export fn testPackedStuff(a: *const PackedStruct, b: *const PackedUnion) void {
}
}
test "self reference through fn ptr field" {
const S = struct {
const A = struct {
f: fn (A) u8,
};
fn foo(a: A) u8 {
_ = a;
return 12;
}
};
var a: S.A = undefined;
a.f = S.foo;
try expect(a.f(a) == 12);
}
test "thread local variable" {
const S = struct {
threadlocal var t: i32 = 1234;
@ -183,19 +150,6 @@ test "thread local variable" {
try expect(S.t == 1235);
}
test "nested optional field in struct" {
const S2 = struct {
y: u8,
};
const S1 = struct {
x: ?S2,
};
var s = S1{
.x = S2{ .y = 127 },
};
try expect(s.x.?.y == 127);
}
fn maybe(x: bool) anyerror!?u32 {
return switch (x) {
true => @as(u32, 42),

View File

@ -2,3 +2,37 @@ const std = @import("std");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
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;
}
};
try expect(S.entry());
comptime try expect(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();
try expect(n.data == null);
}

View File

@ -83,21 +83,6 @@ fn test_cmp_optional_non_optional() !void {
};
}
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;
}
};
try expect(S.entry());
comptime try expect(S.entry());
}
test "unwrap function call with optional pointer return value" {
const S = struct {
fn entry() !void {
@ -139,25 +124,6 @@ test "nested orelse" {
comptime try 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();
try 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| {

View File

@ -20,6 +20,23 @@ test "slicing" {
if (slice_rest.len != 10) unreachable;
}
test "const slice" {
comptime {
const a = "1234567890";
try expect(a.len == 10);
const b = a[1..2];
try expect(b.len == 1);
try expect(b[0] == '2');
}
}
test "comptime slice of undefined pointer of length 0" {
const slice1 = @as([*]i32, undefined)[0..0];
try expect(slice1.len == 0);
const slice2 = @as([*]i32, undefined)[100..100];
try expect(slice2.len == 0);
}
test "slicing zero length array" {
const s1 = ""[0..];
const s2 = ([_]u32{})[0..];
@ -389,3 +406,78 @@ test "type coercion of pointer to anon struct literal to pointer to slice" {
// try S.doTheTest();
comptime try S.doTheTest();
}
test "comptime slice of slice preserves comptime var" {
comptime {
var buff: [10]u8 = undefined;
buff[0..][0..][0] = 1;
try 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;
try expect(buff[0..][0..][0] == 1);
}
}
test "array concat of slices gives slice" {
comptime {
var a: []const u8 = "aoeu";
var b: []const u8 = "asdf";
const c = a ++ b;
try expect(std.mem.eql(u8, c, "aoeuasdf"));
}
}
test "slice of type" {
comptime {
var types_array = [_]type{ i32, f64, type };
for (types_array) |T, i| {
switch (i) {
0 => try expect(T == i32),
1 => try expect(T == f64),
2 => try expect(T == type),
else => unreachable,
}
}
for (types_array[0..]) |T, i| {
switch (i) {
0 => try expect(T == i32),
1 => try expect(T == f64),
2 => try expect(T == type),
else => unreachable,
}
}
}
}
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];
try expect(sliceA[1] == 2);
try 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;
try expect(str.len == 1);
try expect(std.mem.eql(u8, str, "1"));
const str2 = bs ++ "";
try expect(str2.len == 1);
try expect(std.mem.eql(u8, str2, "1"));
}

View File

@ -111,3 +111,91 @@ test "while copies its payload" {
try S.doTheTest();
comptime try S.doTheTest();
}
test "continue and break" {
try runContinueAndBreakTest();
try expect(continue_and_break_counter == 8);
}
var continue_and_break_counter: i32 = 0;
fn runContinueAndBreakTest() !void {
var i: i32 = 0;
while (true) {
continue_and_break_counter += 2;
i += 1;
if (i < 4) {
continue;
}
break;
}
try expect(i == 4);
}
test "while with optional as condition" {
numbers_left = 10;
var sum: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
}
try expect(sum == 45);
}
test "while with optional as condition with else" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
try expect(got_else == 0);
} else {
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
test "while on bool with else result follow else prong" {
const result = while (returnFalse()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 2);
}
test "while on bool with else result follow break prong" {
const result = while (returnTrue()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 10);
}
test "while on optional with else result follow else prong" {
const result = while (returnNull()) |value| {
break value;
} else @as(i32, 2);
try expect(result == 2);
}
test "while on optional with else result follow break prong" {
const result = while (returnOptional(10)) |value| {
break value;
} else @as(i32, 2);
try expect(result == 10);
}
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;
}

View File

@ -1,24 +1,6 @@
const std = @import("std");
const expect = std.testing.expect;
test "continue and break" {
try runContinueAndBreakTest();
try expect(continue_and_break_counter == 8);
}
var continue_and_break_counter: i32 = 0;
fn runContinueAndBreakTest() !void {
var i: i32 = 0;
while (true) {
continue_and_break_counter += 2;
i += 1;
if (i < 4) {
continue;
}
break;
}
try expect(i == 4);
}
test "return with implicit cast from while loop" {
returnWithImplicitCastFromWhileLoopTest() catch unreachable;
}
@ -28,29 +10,6 @@ fn returnWithImplicitCastFromWhileLoopTest() anyerror!void {
}
}
test "while with optional as condition" {
numbers_left = 10;
var sum: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
}
try expect(sum == 45);
}
test "while with optional as condition with else" {
numbers_left = 10;
var sum: i32 = 0;
var got_else: i32 = 0;
while (getNumberOrNull()) |value| {
sum += value;
try expect(got_else == 0);
} else {
got_else += 1;
}
try expect(sum == 45);
try expect(got_else == 1);
}
test "while with error union condition" {
numbers_left = 10;
var sum: i32 = 0;
@ -79,20 +38,6 @@ fn getNumberOrNull() ?i32 {
};
}
test "while on optional with else result follow else prong" {
const result = while (returnNull()) |value| {
break value;
} else @as(i32, 2);
try expect(result == 2);
}
test "while on optional with else result follow break prong" {
const result = while (returnOptional(10)) |value| {
break value;
} else @as(i32, 2);
try expect(result == 10);
}
test "while on error union with else result follow else prong" {
const result = while (returnError()) |value| {
break value;
@ -107,20 +52,6 @@ test "while on error union with else result follow break prong" {
try expect(result == 10);
}
test "while on bool with else result follow else prong" {
const result = while (returnFalse()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 2);
}
test "while on bool with else result follow break prong" {
const result = while (returnTrue()) {
break @as(i32, 10);
} else @as(i32, 2);
try expect(result == 10);
}
fn returnNull() ?i32 {
return null;
}