zig/test/behavior/optional.zig
Andrew Kelley 9dc98fbabb Sema: fix comptime union initialization
The mechanism behind initializing a union's tag is a bit complicated,
depending on whether the union is initialized at runtime,
forced comptime, or implicit comptime.

`coerce_result_ptr` now does not force a block to be a runtime context;
instead of adding runtime instructions directly, it forwards analysis to
the respective functions for initializing optionals and error unions.

`validateUnionInit` now has logic to still emit a runtime
`set_union_tag` instruction even if the union pointer is comptime-known,
for the case of a pointer that is not comptime mutable, such as a
variable or the result of `@intToPtr`.

`validateStructInit` looks for a completely different pattern now; it
now handles the possibility of the corresponding AIR instruction for
the `field_ptr` to be missing or the corresponding `store` to be missing.
See the new comment added to the function for more details. An
equivalent change should probably be made to `validateArrayInit`.

`analyzeOptionalPayloadPtr` and `analyzeErrUnionPayloadPtr` functions now
emit a `optional_payload_ptr_set` or `errunion_payload_ptr_set`
instruction respectively if `initializing` is true and the pointer value
is not comptime-mutable.

`storePtr2` now tries the comptime pointer store before checking if the
element type has one possible value because the comptime pointer store
can have side effects of setting a union tag, setting an optional payload
non-null, or setting an error union to be non-error.

The LLVM backend `lowerParentPtr` function is improved to take into
account the differences in how the LLVM values are lowered depending on
the Zig type. It now handles unions correctly as well as additionally
handling optionals and error unions.

In the LLVM backend, the instructions `optional_payload_ptr_set` and
`errunion_payload_ptr_set` check liveness analysis and only do the side
effects in the case the result of the instruction is unused.

A few wasm and C backend test cases regressed, but they are due to TODOs
in lowering of constants, so this is progress.
2022-02-21 23:49:38 -07:00

336 lines
9.2 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "passing an optional integer as a parameter" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
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());
}
pub const EmptyStruct = struct {};
test "optional pointer to size zero struct" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
var e = EmptyStruct{};
var o: ?*EmptyStruct = &e;
try expect(o != null);
}
test "equality compare optional pointers" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
try testNullPtrsEql();
comptime try testNullPtrsEql();
}
fn testNullPtrsEql() !void {
var number: i32 = 1234;
var x: ?*i32 = null;
var y: ?*i32 = null;
try expect(x == y);
y = &number;
try expect(x != y);
try expect(x != &number);
try expect(&number != x);
x = &number;
try expect(x == y);
try expect(x == &number);
try expect(&number == x);
}
test "optional with void type" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const Foo = struct {
x: ?void,
};
var x = Foo{ .x = null };
try expect(x.x == null);
}
test "address of unwrap optional" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
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;
try expect(foo.a == 1234);
}
test "nested optional field in struct" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const S2 = struct {
y: u8,
};
const S1 = struct {
x: ?S2,
};
var s = S1{
.x = S2{ .y = 127 },
};
try expect(s.x.?.y == 127);
}
test "equality compare optional with non-optional" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
try test_cmp_optional_non_optional();
comptime try 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;
try expect(int_n != ten);
try expect(opt_ten == ten);
try 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: {
try expect(mutable_state == 1);
break :blk2 @as(f64, 5.0);
};
_ = blk1: {
mutable_state += 1;
break :blk1 @as(f64, 10.0);
} != blk2: {
try expect(mutable_state == 2);
break :blk2 @as(?f64, 5.0);
};
}
test "unwrap function call with optional pointer return value" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const S = struct {
fn entry() !void {
try expect(foo().?.* == 1234);
try expect(bar() == null);
}
const global: i32 = 1234;
fn foo() ?*const i32 {
return &global;
}
fn bar() ?*i32 {
return null;
}
};
try S.entry();
comptime try S.entry();
}
test "nested orelse" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const S = struct {
fn entry() !void {
try expect(func() == null);
}
fn maybe() ?Foo {
return null;
}
fn func() ?Foo {
const x = maybe() orelse
maybe() orelse
return null;
_ = x;
unreachable;
}
const Foo = struct {
field: i32,
};
};
try S.entry();
comptime try S.entry();
}
test "self-referential struct through a slice of optional" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
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| {
_ = 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" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
const S = struct {
const Struct = struct {
field: u32,
};
fn doTheTest() !void {
var maybe_dims: ?Struct = null;
maybe_dims = .{ .field = 1 };
try expect(maybe_dims.?.field == 1);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "0-bit child type coerced to optional return ptr result location" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var y = Foo{};
var z = y.thing();
try expect(z != null);
}
const Foo = struct {
pub const Bar = struct {
field: *Foo,
};
pub fn thing(self: *Foo) ?Bar {
return Bar{ .field = self };
}
};
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "0-bit child type coerced to optional" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var it: Foo = .{
.list = undefined,
};
try 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;
}
};
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "array of optional unaligned types" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
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;
try expectEqual(Enum.one, values[i].?.Num);
i += 1;
try expectEqual(Enum.two, values[i].?.Num);
i += 1;
try expectEqual(Enum.three, values[i].?.Num);
i += 1;
try expectEqual(Enum.one, values[i].?.Num);
i += 1;
try expectEqual(Enum.two, values[i].?.Num);
i += 1;
try expectEqual(Enum.three, values[i].?.Num);
}