From c711c788f0a840f45d0d7423efe2f946b47caafb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 May 2022 15:10:18 -0700 Subject: [PATCH] stage2: fixes for error unions, optionals, errors * `?E` where E is an error set with only one field now lowers the same as `bool`. * Fix implementation of errUnionErrOffset and errUnionPayloadOffset to properly compute the offset of each field. Also name them the same as the corresponding LLVM functions and have the same function signature, to avoid confusion. This fixes a bug where wasm was passing the error union type instead of the payload type. * Fix C backend handling of optionals with zero-bit payload types. * C backend: separate out airOptionalPayload and airOptionalPayloadPtr which reduces branching and cleans up control flow. * Make Type.isNoReturn return true for error sets with no fields. * Make `?error{}` have only one possible value (null). --- src/Sema.zig | 11 ++++- src/arch/aarch64/CodeGen.zig | 4 +- src/arch/arm/CodeGen.zig | 6 +-- src/arch/wasm/CodeGen.zig | 18 ++++---- src/arch/x86_64/CodeGen.zig | 16 +++---- src/codegen.zig | 28 +++++++----- src/codegen/c.zig | 88 +++++++++++++++++++++++------------- src/type.zig | 64 ++++++++++++++++++++++---- test/behavior/error.zig | 34 +++++++++++++- 9 files changed, 192 insertions(+), 77 deletions(-) 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