From 1e5a494603d287ea3005dc35f0528c0311f43515 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 9 Feb 2022 18:19:03 -0700 Subject: [PATCH] Sema: implement comptime ptr store to optional payload and error union payload --- src/Sema.zig | 70 ++++++++++++- test/behavior.zig | 4 +- test/behavior/optional.zig | 163 ++++++++++++++++++++++++++++++ test/behavior/optional_llvm.zig | 27 ----- test/behavior/optional_stage1.zig | 108 -------------------- 5 files changed, 232 insertions(+), 140 deletions(-) delete mode 100644 test/behavior/optional_llvm.zig delete mode 100644 test/behavior/optional_stage1.zig diff --git a/src/Sema.zig b/src/Sema.zig index 4d38c6b7f7..3b4f1c6f55 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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, } diff --git a/test/behavior.zig b/test/behavior.zig index b2ffbabde2..525ae5b2a1 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -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"); diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index d6d6249c6e..3caf777195 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -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); +} diff --git a/test/behavior/optional_llvm.zig b/test/behavior/optional_llvm.zig deleted file mode 100644 index 9fdf703a42..0000000000 --- a/test/behavior/optional_llvm.zig +++ /dev/null @@ -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); -} diff --git a/test/behavior/optional_stage1.zig b/test/behavior/optional_stage1.zig deleted file mode 100644 index 04c51f8d9d..0000000000 --- a/test/behavior/optional_stage1.zig +++ /dev/null @@ -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); -}