Commit c711c788f0
Changed files (9)
src
arch
codegen
test
behavior
src/arch/aarch64/CodeGen.zig
@@ -30,7 +30,7 @@ const DebugInfoOutput = codegen.DebugInfoOutput;
const bits = @import("bits.zig");
const abi = @import("abi.zig");
const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
-const errUnionErrOffset = codegen.errUnionErrOffset;
+const errUnionErrorOffset = codegen.errUnionErrorOffset;
const RegisterManager = abi.RegisterManager;
const RegisterLock = RegisterManager.RegisterLock;
const Register = bits.Register;
@@ -3615,7 +3615,7 @@ fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
return MCValue{ .immediate = 0 }; // always false
}
- const err_off = errUnionErrOffset(ty, self.target.*);
+ const err_off = errUnionErrorOffset(payload_type, self.target.*);
switch (operand) {
.stack_offset => |off| {
const offset = off - @intCast(u32, err_off);
src/arch/arm/CodeGen.zig
@@ -30,7 +30,7 @@ const DebugInfoOutput = codegen.DebugInfoOutput;
const bits = @import("bits.zig");
const abi = @import("abi.zig");
const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
-const errUnionErrOffset = codegen.errUnionErrOffset;
+const errUnionErrorOffset = codegen.errUnionErrorOffset;
const RegisterManager = abi.RegisterManager;
const RegisterLock = RegisterManager.RegisterLock;
const Register = bits.Register;
@@ -1775,7 +1775,7 @@ fn errUnionErr(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCV
return error_union_mcv;
}
- const err_offset = @intCast(u32, errUnionErrOffset(error_union_ty, self.target.*));
+ const err_offset = @intCast(u32, errUnionErrorOffset(payload_ty, self.target.*));
switch (error_union_mcv) {
.register => return self.fail("TODO errUnionErr for registers", .{}),
.stack_argument_offset => |off| {
@@ -1812,7 +1812,7 @@ fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type)
return MCValue.none;
}
- const payload_offset = @intCast(u32, errUnionPayloadOffset(error_union_ty, self.target.*));
+ const payload_offset = @intCast(u32, errUnionPayloadOffset(payload_ty, self.target.*));
switch (error_union_mcv) {
.register => return self.fail("TODO errUnionPayload for registers", .{}),
.stack_argument_offset => |off| {
src/arch/wasm/CodeGen.zig
@@ -23,7 +23,7 @@ const Mir = @import("Mir.zig");
const Emit = @import("Emit.zig");
const abi = @import("abi.zig");
const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
-const errUnionErrOffset = codegen.errUnionErrOffset;
+const errUnionErrorOffset = codegen.errUnionErrorOffset;
/// Wasm Value, created when generating an instruction
const WValue = union(enum) {
@@ -2919,10 +2919,10 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
- const err_ty = self.air.typeOf(un_op);
- const pl_ty = err_ty.errorUnionPayload();
+ const err_union_ty = self.air.typeOf(un_op);
+ const pl_ty = err_union_ty.errorUnionPayload();
- if (err_ty.errorUnionSet().errorSetCardinality() == .zero) {
+ if (err_union_ty.errorUnionSet().errorSetCardinality() == .zero) {
switch (opcode) {
.i32_ne => return WValue{ .imm32 = 0 },
.i32_eq => return WValue{ .imm32 = 1 },
@@ -2933,7 +2933,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!W
try self.emitWValue(operand);
if (pl_ty.hasRuntimeBitsIgnoreComptime()) {
try self.addMemArg(.i32_load16_u, .{
- .offset = operand.offset() + @intCast(u32, errUnionErrOffset(pl_ty, self.target)),
+ .offset = operand.offset() + @intCast(u32, errUnionErrorOffset(pl_ty, self.target)),
.alignment = Type.anyerror.abiAlignment(self.target),
});
}
@@ -2985,7 +2985,7 @@ fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index, op_is_ptr: bool) In
return operand;
}
- return self.load(operand, Type.anyerror, @intCast(u32, errUnionErrOffset(payload_ty, self.target)));
+ return self.load(operand, Type.anyerror, @intCast(u32, errUnionErrorOffset(payload_ty, self.target)));
}
fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -3011,7 +3011,7 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
// ensure we also write '0' to the error part, so any present stack value gets overwritten by it.
try self.emitWValue(err_union);
try self.addImm32(0);
- const err_val_offset = @intCast(u32, errUnionErrOffset(pl_ty, self.target));
+ const err_val_offset = @intCast(u32, errUnionErrorOffset(pl_ty, self.target));
try self.addMemArg(.i32_store16, .{ .offset = err_union.offset() + err_val_offset, .alignment = 2 });
return err_union;
@@ -3031,7 +3031,7 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const err_union = try self.allocStack(err_ty);
// store error value
- try self.store(err_union, operand, Type.anyerror, @intCast(u32, errUnionErrOffset(pl_ty, self.target)));
+ try self.store(err_union, operand, Type.anyerror, @intCast(u32, errUnionErrorOffset(pl_ty, self.target)));
// write 'undefined' to the payload
const payload_ptr = try self.buildPointerOffset(err_union, @intCast(u32, errUnionPayloadOffset(pl_ty, self.target)), .new);
@@ -3986,7 +3986,7 @@ fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue
operand,
.{ .imm32 = 0 },
Type.anyerror,
- @intCast(u32, errUnionErrOffset(payload_ty, self.target)),
+ @intCast(u32, errUnionErrorOffset(payload_ty, self.target)),
);
if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
src/arch/x86_64/CodeGen.zig
@@ -30,7 +30,7 @@ const Value = @import("../../value.zig").Value;
const bits = @import("bits.zig");
const abi = @import("abi.zig");
const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
-const errUnionErrOffset = codegen.errUnionErrOffset;
+const errUnionErrorOffset = codegen.errUnionErrorOffset;
const callee_preserved_regs = abi.callee_preserved_regs;
const caller_preserved_regs = abi.caller_preserved_regs;
@@ -1799,7 +1799,7 @@ fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
break :result operand;
}
- const err_off = errUnionErrOffset(err_union_ty, self.target.*);
+ const err_off = errUnionErrorOffset(payload_ty, self.target.*);
switch (operand) {
.stack_offset => |off| {
const offset = off - @intCast(i32, err_off);
@@ -1844,7 +1844,7 @@ fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
break :result MCValue.none;
}
- const payload_off = errUnionPayloadOffset(err_union_ty, self.target.*);
+ const payload_off = errUnionPayloadOffset(payload_ty, self.target.*);
switch (operand) {
.stack_offset => |off| {
const offset = off - @intCast(i32, payload_off);
@@ -1978,8 +1978,8 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
const abi_size = @intCast(u32, error_union_ty.abiSize(self.target.*));
const abi_align = error_union_ty.abiAlignment(self.target.*);
const stack_offset = @intCast(i32, try self.allocMem(inst, abi_size, abi_align));
- const payload_off = errUnionPayloadOffset(error_union_ty, self.target.*);
- const err_off = errUnionErrOffset(error_union_ty, self.target.*);
+ const payload_off = errUnionPayloadOffset(payload_ty, self.target.*);
+ const err_off = errUnionErrorOffset(payload_ty, self.target.*);
try self.genSetStack(payload_ty, stack_offset - @intCast(i32, payload_off), operand, .{});
try self.genSetStack(Type.anyerror, stack_offset - @intCast(i32, err_off), .{ .immediate = 0 }, .{});
@@ -2007,8 +2007,8 @@ fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
const abi_size = @intCast(u32, error_union_ty.abiSize(self.target.*));
const abi_align = error_union_ty.abiAlignment(self.target.*);
const stack_offset = @intCast(i32, try self.allocMem(inst, abi_size, abi_align));
- const payload_off = errUnionPayloadOffset(error_union_ty, self.target.*);
- const err_off = errUnionErrOffset(error_union_ty, self.target.*);
+ const payload_off = errUnionPayloadOffset(payload_ty, self.target.*);
+ const err_off = errUnionErrorOffset(payload_ty, self.target.*);
try self.genSetStack(Type.anyerror, stack_offset - @intCast(i32, err_off), operand, .{});
try self.genSetStack(payload_ty, stack_offset - @intCast(i32, payload_off), .undef, .{});
@@ -4670,7 +4670,7 @@ fn isErr(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue
try self.spillCompareFlagsIfOccupied();
self.compare_flags_inst = inst;
- const err_off = errUnionErrOffset(ty, self.target.*);
+ const err_off = errUnionErrorOffset(ty.errorUnionPayload(), self.target.*);
switch (operand) {
.stack_offset => |off| {
const offset = off - @intCast(i32, err_off);
src/codegen/c.zig
@@ -711,21 +711,24 @@ pub const DeclGen = struct {
.Bool => return writer.print("{}", .{val.toBool()}),
.Optional => {
var opt_buf: Type.Payload.ElemType = undefined;
- const payload_type = ty.optionalChild(&opt_buf);
- if (ty.optionalReprIsPayload()) {
- return dg.renderValue(writer, payload_type, val, location);
- }
- if (payload_type.abiSize(target) == 0) {
+ const payload_ty = ty.optionalChild(&opt_buf);
+
+ if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
const is_null = val.castTag(.opt_payload) == null;
return writer.print("{}", .{is_null});
}
+
+ if (ty.optionalReprIsPayload()) {
+ return dg.renderValue(writer, payload_ty, val, location);
+ }
+
try writer.writeByte('(');
try dg.renderTypecast(writer, ty);
try writer.writeAll("){");
if (val.castTag(.opt_payload)) |pl| {
const payload_val = pl.data;
try writer.writeAll(" .is_null = false, .payload = ");
- try dg.renderValue(writer, payload_type, payload_val, location);
+ try dg.renderValue(writer, payload_ty, payload_val, location);
try writer.writeAll(" }");
} else {
try writer.writeAll(" .is_null = true }");
@@ -1360,12 +1363,12 @@ pub const DeclGen = struct {
var opt_buf: Type.Payload.ElemType = undefined;
const child_type = t.optionalChild(&opt_buf);
- if (t.optionalReprIsPayload()) {
- return dg.renderType(w, child_type);
+ if (!child_type.hasRuntimeBitsIgnoreComptime()) {
+ return w.writeAll("bool");
}
- if (child_type.abiSize(target) == 0) {
- return w.writeAll("bool");
+ if (t.optionalReprIsPayload()) {
+ return dg.renderType(w, child_type);
}
const name = dg.getTypedefName(t) orelse
@@ -1816,8 +1819,9 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.not => try airNot (f, inst),
.optional_payload => try airOptionalPayload(f, inst),
- .optional_payload_ptr => try airOptionalPayload(f, inst),
+ .optional_payload_ptr => try airOptionalPayloadPtr(f, inst),
.optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst),
+ .wrap_optional => try airWrapOptional(f, inst),
.is_err => try airIsErr(f, inst, false, "!="),
.is_non_err => try airIsErr(f, inst, false, "=="),
@@ -1846,7 +1850,6 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.cond_br => try airCondBr(f, inst),
.br => try airBr(f, inst),
.switch_br => try airSwitchBr(f, inst),
- .wrap_optional => try airWrapOptional(f, inst),
.struct_field_ptr => try airStructFieldPtr(f, inst),
.array_to_slice => try airArrayToSlice(f, inst),
.cmpxchg_weak => try airCmpxchg(f, inst, "weak"),
@@ -3145,7 +3148,6 @@ fn airIsNull(
const un_op = f.air.instructions.items(.data)[inst].un_op;
const writer = f.object.writer();
const operand = try f.resolveInst(un_op);
- const target = f.object.dg.module.getTarget();
const local = try f.allocLocal(Type.initTag(.bool), .Const);
try writer.writeAll(" = (");
@@ -3153,18 +3155,18 @@ fn airIsNull(
const ty = f.air.typeOf(un_op);
var opt_buf: Type.Payload.ElemType = undefined;
- const payload_type = if (ty.zigTypeTag() == .Pointer)
+ const payload_ty = if (ty.zigTypeTag() == .Pointer)
ty.childType().optionalChild(&opt_buf)
else
ty.optionalChild(&opt_buf);
- if (ty.isPtrLikeOptional()) {
+ if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
+ try writer.print("){s} {s} true;\n", .{ deref_suffix, operator });
+ } else if (ty.isPtrLikeOptional()) {
// operand is a regular pointer, test `operand !=/== NULL`
try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator });
- } else if (payload_type.zigTypeTag() == .ErrorSet) {
+ } else if (payload_ty.zigTypeTag() == .ErrorSet) {
try writer.print("){s} {s} 0;\n", .{ deref_suffix, operator });
- } else if (payload_type.abiSize(target) == 0) {
- try writer.print("){s} {s} true;\n", .{ deref_suffix, operator });
} else {
try writer.print("){s}.is_null {s} true;\n", .{ deref_suffix, operator });
}
@@ -3172,18 +3174,46 @@ fn airIsNull(
}
fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue {
- if (f.liveness.isUnused(inst))
+ if (f.liveness.isUnused(inst)) return CValue.none;
+
+ const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+ const writer = f.object.writer();
+ const operand = try f.resolveInst(ty_op.operand);
+ const opt_ty = f.air.typeOf(ty_op.operand);
+
+ var buf: Type.Payload.ElemType = undefined;
+ const payload_ty = opt_ty.optionalChild(&buf);
+
+ if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
return CValue.none;
+ }
+
+ if (opt_ty.optionalReprIsPayload()) {
+ return operand;
+ }
+
+ const inst_ty = f.air.typeOfIndex(inst);
+ const local = try f.allocLocal(inst_ty, .Const);
+ try writer.writeAll(" = (");
+ try f.writeCValue(writer, operand);
+ try writer.writeAll(").payload;\n");
+ return local;
+}
+
+fn airOptionalPayloadPtr(f: *Function, inst: Air.Inst.Index) !CValue {
+ if (f.liveness.isUnused(inst)) return CValue.none;
const ty_op = f.air.instructions.items(.data)[inst].ty_op;
const writer = f.object.writer();
const operand = try f.resolveInst(ty_op.operand);
- const operand_ty = f.air.typeOf(ty_op.operand);
+ const ptr_ty = f.air.typeOf(ty_op.operand);
+ const opt_ty = ptr_ty.childType();
+ var buf: Type.Payload.ElemType = undefined;
+ const payload_ty = opt_ty.optionalChild(&buf);
- const opt_ty = if (operand_ty.zigTypeTag() == .Pointer)
- operand_ty.elemType()
- else
- operand_ty;
+ if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
+ return operand;
+ }
if (opt_ty.optionalReprIsPayload()) {
// the operand is just a regular pointer, no need to do anything special.
@@ -3192,14 +3222,10 @@ fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue {
}
const inst_ty = f.air.typeOfIndex(inst);
- const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
- const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else "";
-
const local = try f.allocLocal(inst_ty, .Const);
- try writer.print(" = {s}(", .{maybe_addrof});
+ try writer.writeAll(" = &(");
try f.writeCValue(writer, operand);
-
- try writer.print("){s}payload;\n", .{maybe_deref});
+ try writer.writeAll(")->payload;\n");
return local;
}
src/codegen.zig
@@ -891,18 +891,22 @@ fn lowerDeclRef(
return Result{ .appended = {} };
}
-pub fn errUnionPayloadOffset(ty: Type, target: std.Target) u64 {
- const payload_ty = ty.errorUnionPayload();
- return if (Type.anyerror.abiAlignment(target) >= payload_ty.abiAlignment(target))
- Type.anyerror.abiSize(target)
- else
- 0;
+pub fn errUnionPayloadOffset(payload_ty: Type, target: std.Target) u64 {
+ const payload_align = payload_ty.abiAlignment(target);
+ const error_align = Type.anyerror.abiAlignment(target);
+ if (payload_align >= error_align) {
+ return 0;
+ } else {
+ return mem.alignForwardGeneric(u64, Type.anyerror.abiSize(target), payload_align);
+ }
}
-pub fn errUnionErrOffset(ty: Type, target: std.Target) u64 {
- const payload_ty = ty.errorUnionPayload();
- return if (Type.anyerror.abiAlignment(target) >= payload_ty.abiAlignment(target))
- 0
- else
- payload_ty.abiSize(target);
+pub fn errUnionErrorOffset(payload_ty: Type, target: std.Target) u64 {
+ const payload_align = payload_ty.abiAlignment(target);
+ const error_align = Type.anyerror.abiAlignment(target);
+ if (payload_align >= error_align) {
+ return mem.alignForwardGeneric(u64, payload_ty.abiSize(target), error_align);
+ } else {
+ return 0;
+ }
}
src/Sema.zig
@@ -23316,7 +23316,6 @@ pub fn typeHasOnePossibleValue(
.const_slice,
.mut_slice,
.anyopaque,
- .optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
.enum_literal,
@@ -23351,6 +23350,16 @@ pub fn typeHasOnePossibleValue(
.bound_fn,
=> return null,
+ .optional => {
+ var buf: Type.Payload.ElemType = undefined;
+ const child_ty = ty.optionalChild(&buf);
+ if (child_ty.isNoReturn()) {
+ return Value.@"null";
+ } else {
+ return null;
+ }
+ },
+
.error_set_single => {
const name = ty.castTag(.error_set_single).?.data;
return try Value.Tag.@"error".create(sema.arena, .{ .name = name });
src/type.zig
@@ -2375,7 +2375,6 @@ pub const Type = extern union {
// These types have more than one possible value, so the result is the same as
// asking whether they are comptime-only types.
.anyframe_T,
- .optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
.single_const_pointer,
@@ -2397,6 +2396,22 @@ pub const Type = extern union {
}
},
+ .optional => {
+ var buf: Payload.ElemType = undefined;
+ const child_ty = ty.optionalChild(&buf);
+ if (child_ty.isNoReturn()) {
+ // Then the optional is comptime-known to be null.
+ return false;
+ }
+ if (ignore_comptime_only) {
+ return true;
+ } else if (sema_kit) |sk| {
+ return !(try sk.sema.typeRequiresComptime(sk.block, sk.src, child_ty));
+ } else {
+ return !comptimeOnly(child_ty);
+ }
+ },
+
.error_union => {
// This code needs to be kept in sync with the equivalent switch prong
// in abiSizeAdvanced.
@@ -2665,13 +2680,22 @@ pub const Type = extern union {
};
}
- pub fn isNoReturn(self: Type) bool {
- const definitely_correct_result =
- self.tag_if_small_enough != .bound_fn and
- self.zigTypeTag() == .NoReturn;
- const fast_result = self.tag_if_small_enough == Tag.noreturn;
- assert(fast_result == definitely_correct_result);
- return fast_result;
+ /// TODO add enums with no fields here
+ pub fn isNoReturn(ty: Type) bool {
+ switch (ty.tag()) {
+ .noreturn => return true,
+ .error_set => {
+ const err_set_obj = ty.castTag(.error_set).?.data;
+ const names = err_set_obj.names.keys();
+ return names.len == 0;
+ },
+ .error_set_merged => {
+ const name_map = ty.castTag(.error_set_merged).?.data;
+ const names = name_map.keys();
+ return names.len == 0;
+ },
+ else => return false,
+ }
}
/// Returns 0 if the pointer is naturally aligned and the element type is 0-bit.
@@ -2918,7 +2942,13 @@ pub const Type = extern union {
switch (child_type.zigTypeTag()) {
.Pointer => return AbiAlignmentAdvanced{ .scalar = @divExact(target.cpu.arch.ptrBitWidth(), 8) },
- .ErrorSet => return abiAlignmentAdvanced(Type.anyerror, target, strat),
+ .ErrorSet => switch (child_type.errorSetCardinality()) {
+ // `?error{}` is comptime-known to be null.
+ .zero => return AbiAlignmentAdvanced{ .scalar = 0 },
+ .one => return AbiAlignmentAdvanced{ .scalar = 1 },
+ .many => return abiAlignmentAdvanced(Type.anyerror, target, strat),
+ },
+ .NoReturn => return AbiAlignmentAdvanced{ .scalar = 0 },
else => {},
}
@@ -3365,6 +3395,11 @@ pub const Type = extern union {
.optional => {
var buf: Payload.ElemType = undefined;
const child_type = ty.optionalChild(&buf);
+
+ if (child_type.isNoReturn()) {
+ return AbiSizeAdvanced{ .scalar = 0 };
+ }
+
if (!child_type.hasRuntimeBits()) return AbiSizeAdvanced{ .scalar = 1 };
switch (child_type.zigTypeTag()) {
@@ -4804,7 +4839,6 @@ pub const Type = extern union {
.const_slice,
.mut_slice,
.anyopaque,
- .optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
.enum_literal,
@@ -4839,6 +4873,16 @@ pub const Type = extern union {
.bound_fn,
=> return null,
+ .optional => {
+ var buf: Payload.ElemType = undefined;
+ const child_ty = ty.optionalChild(&buf);
+ if (child_ty.isNoReturn()) {
+ return Value.@"null";
+ } else {
+ return null;
+ }
+ },
+
.error_set_single => return Value.initTag(.the_only_possible_value),
.error_set => {
const err_set_obj = ty.castTag(.error_set).?.data;
test/behavior/error.zig
@@ -121,7 +121,7 @@ test "debug info for optional error set" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
- const SomeError = error{Hello};
+ const SomeError = error{ Hello, Hello2 };
var a_local_variable: ?SomeError = null;
_ = a_local_variable;
}
@@ -454,6 +454,38 @@ test "optional error set is the same size as error set" {
comptime try expect(S.returnsOptErrSet() == null);
}
+test "optional error set with only one error is the same size as bool" {
+ if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+ if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
+ const E = error{only};
+ comptime try expect(@sizeOf(?E) == @sizeOf(bool));
+ comptime try expect(@alignOf(?E) == @alignOf(bool));
+ const S = struct {
+ fn gimmeNull() ?E {
+ return null;
+ }
+ fn gimmeErr() ?E {
+ return error.only;
+ }
+ };
+ try expect(S.gimmeNull() == null);
+ try expect(error.only == S.gimmeErr().?);
+ comptime try expect(S.gimmeNull() == null);
+ comptime try expect(error.only == S.gimmeErr().?);
+}
+
+test "optional empty error set" {
+ if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+
+ const T = ?error{};
+ var t: T = undefined;
+ if (t != null) {
+ @compileError("test failed");
+ }
+}
+
test "nested catch" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO