diff --git a/src/Sema.zig b/src/Sema.zig index b718912a38..b0c3c17483 100644 --- a/src/Sema.zig +++ b/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 }); diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 5f358efb09..2a71f3138a 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/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); diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3d69e4022b..b7682a5b9a 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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| { diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index cfa2c8bb4e..1eddb7441b 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/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 = {} }; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index ba550f6d82..5c69f78724 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/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); diff --git a/src/codegen.zig b/src/codegen.zig index 86f2613b5f..fbe462959e 100644 --- a/src/codegen.zig +++ b/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; + } } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1b6708c1cf..1e45090648 100644 --- a/src/codegen/c.zig +++ b/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)) - return CValue.none; + 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 opt_ty = f.air.typeOf(ty_op.operand); - const opt_ty = if (operand_ty.zigTypeTag() == .Pointer) - operand_ty.elemType() - else - operand_ty; + 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 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); + + 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; } diff --git a/src/type.zig b/src/type.zig index 1c59cf9e59..4325d6d772 100644 --- a/src/type.zig +++ b/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; diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 83a9384d71..4f316aeab2 100644 --- a/test/behavior/error.zig +++ b/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