Commit 7999374b21

Andrew Kelley <andrew@ziglang.org>
2025-06-30 01:39:26
Sema: correct OPV for optional empty error set
prevents crashes in backends; improves codegen; provides more comptime-ness.
1 parent e837765
Changed files (3)
src/Sema.zig
@@ -9065,10 +9065,14 @@ fn zirOptionalPayload(
     };
 
     if (try sema.resolveDefinedValue(block, src, operand)) |val| {
-        return if (val.optionalValue(zcu)) |payload|
-            Air.internedToRef(payload.toIntern())
-        else
-            sema.fail(block, src, "unable to unwrap null", .{});
+        if (val.optionalValue(zcu)) |payload| return Air.internedToRef(payload.toIntern());
+        if (block.isComptime()) return sema.fail(block, src, "unable to unwrap null", .{});
+        if (safety_check and block.wantSafety()) {
+            try sema.safetyPanic(block, src, .unwrap_null);
+        } else {
+            _ = try block.addNoOp(.unreach);
+        }
+        return .unreachable_value;
     }
 
     try sema.requireRuntimeBlock(block, src, null);
@@ -36443,7 +36447,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
             .type_int_unsigned, // u0 handled above
             .type_pointer,
             .type_slice,
-            .type_optional, // ?noreturn handled above
             .type_anyframe,
             .type_error_union,
             .type_anyerror_union,
@@ -36655,6 +36658,15 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
 
                 else => unreachable,
             },
+
+            .type_optional => {
+                const payload_ip = ip.indexToKey(ty.toIntern()).opt_type;
+                // Although ?noreturn is handled above, the element type
+                // can be effectively noreturn for example via an empty
+                // enum or error set.
+                if (ip.isNoReturn(payload_ip)) return try pt.nullValue(ty);
+                return null;
+            },
         },
     };
 }
test/cases/compile_errors/optional_empty_error_set.zig
@@ -0,0 +1,13 @@
+export fn example() void {
+    comptime foo() catch |err| switch (err) {};
+}
+var x: ?error{} = null;
+fn foo() !void {
+    return x.?;
+}
+// error
+// backend=stage2
+// target=native
+//
+// :6:13: error: unable to unwrap null
+// :2:17: note: called at comptime here
test/cases/safety/optional_empty_error_set.zig
@@ -0,0 +1,22 @@
+const std = @import("std");
+
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, ra: ?usize) noreturn {
+    _ = stack_trace;
+    _ = ra;
+    if (std.mem.eql(u8, message, "attempt to use null value")) {
+        std.process.exit(0);
+    }
+    std.process.exit(1);
+}
+
+pub fn main() !void {
+    foo() catch |err| switch (err) {};
+    return error.TestFailed;
+}
+var x: ?error{} = null;
+fn foo() !void {
+    return x.?;
+}
+// run
+// backend=stage2,llvm
+// target=native