Sema: implement comptime ptr store to optional payload

and error union payload
This commit is contained in:
Andrew Kelley 2022-02-09 18:19:03 -07:00
parent 7f0cf395aa
commit 1e5a494603
5 changed files with 232 additions and 140 deletions

View File

@ -15069,8 +15069,74 @@ fn beginComptimePtrMutation(
else => unreachable,
}
},
.eu_payload_ptr => return sema.fail(block, src, "TODO comptime store to eu_payload_ptr", .{}),
.opt_payload_ptr => return sema.fail(block, src, "TODO comptime store opt_payload_ptr", .{}),
.eu_payload_ptr => {
const eu_ptr_val = ptr_val.castTag(.eu_payload_ptr).?.data;
var parent = try beginComptimePtrMutation(sema, block, src, eu_ptr_val);
const payload_ty = parent.ty.errorUnionPayload();
switch (parent.val.tag()) {
else => {
// An error union has been initialized to undefined at comptime and now we
// are for the first time setting the payload. We must change the
// representation of the error union from `undef` to `opt_payload`.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const payload = try arena.create(Value.Payload.SubValue);
payload.* = .{
.base = .{ .tag = .eu_payload },
.data = Value.undef,
};
parent.val.* = Value.initPayload(&payload.base);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &payload.data,
.ty = payload_ty,
};
},
.eu_payload => return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &parent.val.castTag(.eu_payload).?.data,
.ty = payload_ty,
},
}
},
.opt_payload_ptr => {
const opt_ptr_val = ptr_val.castTag(.opt_payload_ptr).?.data;
var parent = try beginComptimePtrMutation(sema, block, src, opt_ptr_val);
const payload_ty = try parent.ty.optionalChildAlloc(sema.arena);
switch (parent.val.tag()) {
.undef, .null_value => {
// An optional has been initialized to undefined at comptime and now we
// are for the first time setting the payload. We must change the
// representation of the optional from `undef` to `opt_payload`.
const arena = parent.beginArena(sema.gpa);
defer parent.finishArena();
const payload = try arena.create(Value.Payload.SubValue);
payload.* = .{
.base = .{ .tag = .opt_payload },
.data = Value.undef,
};
parent.val.* = Value.initPayload(&payload.base);
return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &payload.data,
.ty = payload_ty,
};
},
.opt_payload => return ComptimePtrMutationKit{
.decl_ref_mut = parent.decl_ref_mut,
.val = &parent.val.castTag(.opt_payload).?.data,
.ty = payload_ty,
},
else => unreachable,
}
},
.decl_ref => unreachable, // isComptimeMutablePtr() has been checked already
else => unreachable,
}

View File

@ -33,7 +33,7 @@ test {
_ = @import("behavior/hasdecl.zig");
_ = @import("behavior/hasfield.zig");
_ = @import("behavior/namespace_depends_on_compile_var.zig");
_ = @import("behavior/optional_llvm.zig");
_ = @import("behavior/optional.zig");
_ = @import("behavior/prefetch.zig");
_ = @import("behavior/pub_enum.zig");
_ = @import("behavior/slice_sentinel_comptime.zig");
@ -69,7 +69,6 @@ test {
_ = @import("behavior/inttoptr.zig");
_ = @import("behavior/member_func.zig");
_ = @import("behavior/null.zig");
_ = @import("behavior/optional.zig");
_ = @import("behavior/pointers.zig");
_ = @import("behavior/ptrcast.zig");
_ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
@ -154,7 +153,6 @@ test {
_ = @import("behavior/ir_block_deps.zig");
_ = @import("behavior/misc.zig");
_ = @import("behavior/muladd.zig");
_ = @import("behavior/optional_stage1.zig");
_ = @import("behavior/popcount_stage1.zig");
_ = @import("behavior/reflection.zig");
_ = @import("behavior/select.zig");

View File

@ -1,9 +1,13 @@
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_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;
@ -21,12 +25,18 @@ test "passing an optional integer as a parameter" {
pub const EmptyStruct = struct {};
test "optional pointer to size zero struct" {
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();
}
@ -48,6 +58,9 @@ fn testNullPtrsEql() !void {
}
test "optional with void type" {
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,
};
@ -56,6 +69,9 @@ test "optional with void type" {
}
test "address of unwrap optional" {
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,
@ -73,6 +89,9 @@ test "address of unwrap optional" {
}
test "nested optional field in struct" {
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,
};
@ -86,6 +105,9 @@ test "nested optional field in struct" {
}
test "equality compare optional with non-optional" {
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();
}
@ -120,6 +142,9 @@ fn test_cmp_optional_non_optional() !void {
}
test "unwrap function call with optional pointer return value" {
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);
@ -138,6 +163,9 @@ test "unwrap function call with optional pointer return value" {
}
test "nested orelse" {
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);
@ -159,3 +187,138 @@ test "nested orelse" {
try S.entry();
comptime try S.entry();
}
test "self-referential struct through a slice of optional" {
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);
}

View File

@ -1,27 +0,0 @@
const std = @import("std");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
const builtin = @import("builtin");
test "self-referential struct through a slice of optional" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
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

@ -1,108 +0,0 @@
const std = @import("std");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
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" {
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" {
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" {
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" {
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);
}