authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-06-30 15:25:35+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-06-30 15:25:35+02:00
log79db39307bea14c9d43b365535e704d2a86f3b24
treee8e28732fab6a70ca261dbb13654be44a202881e
parente8377659560497beb36ec1f981b024f74292c1b9
parentd9742acc232a2af4f98e0fbb9f749737a126e6d1
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24297 from ziglang/optional-empty-error-set

Sema: correct OPV for optional empty error set

4 files changed, 80 insertions(+), 20 deletions(-)

src/Air.zig+15-7
......@@ -1141,6 +1141,20 @@ pub const Inst = struct {
11411141 pub fn toType(ref: Ref) Type {
11421142 return .fromInterned(ref.toInterned().?);
11431143 }
1144
1145 pub fn fromIntern(ip_index: InternPool.Index) Ref {
1146 return switch (ip_index) {
1147 .none => .none,
1148 else => {
1149 assert(@intFromEnum(ip_index) >> 31 == 0);
1150 return @enumFromInt(@as(u31, @intCast(@intFromEnum(ip_index))));
1151 },
1152 };
1153 }
1154
1155 pub fn fromValue(v: Value) Ref {
1156 return .fromIntern(v.toIntern());
1157 }
11441158 };
11451159
11461160 /// All instructions have an 8-byte payload, which is contained within
......@@ -1754,13 +1768,7 @@ pub fn deinit(air: *Air, gpa: std.mem.Allocator) void {
17541768}
17551769
17561770pub fn internedToRef(ip_index: InternPool.Index) Inst.Ref {
1757 return switch (ip_index) {
1758 .none => .none,
1759 else => {
1760 assert(@intFromEnum(ip_index) >> 31 == 0);
1761 return @enumFromInt(@as(u31, @intCast(@intFromEnum(ip_index))));
1762 },
1763 };
1771 return .fromIntern(ip_index);
17641772}
17651773
17661774/// Returns `null` if runtime-known.
src/Sema.zig+30-13
......@@ -8060,7 +8060,7 @@ fn analyzeCall(
80608060 };
80618061
80628062 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + runtime_args.len);
8063 const result = try block.addInst(.{
8063 const maybe_opv = try block.addInst(.{
80648064 .tag = call_tag,
80658065 .data = .{ .pl_op = .{
80668066 .operand = runtime_func,
......@@ -8072,7 +8072,7 @@ fn analyzeCall(
80728072 sema.appendRefsAssumeCapacity(runtime_args);
80738073
80748074 if (ensure_result_used) {
8075 try sema.ensureResultUsed(block, sema.typeOf(result), call_src);
8075 try sema.ensureResultUsed(block, sema.typeOf(maybe_opv), call_src);
80768076 }
80778077
80788078 if (call_tag == .call_always_tail) {
......@@ -8082,10 +8082,10 @@ fn analyzeCall(
80828082 .pointer => func_or_ptr_ty.childType(zcu),
80838083 else => unreachable,
80848084 };
8085 return sema.handleTailCall(block, call_src, runtime_func_ty, result);
8085 return sema.handleTailCall(block, call_src, runtime_func_ty, maybe_opv);
80868086 }
80878087
8088 if (resolved_ret_ty.toIntern() == .noreturn_type) {
8088 if (ip.isNoReturn(resolved_ret_ty.toIntern())) {
80898089 const want_check = c: {
80908090 if (!block.wantSafety()) break :c false;
80918091 if (func_val != null) break :c false;
......@@ -8099,6 +8099,11 @@ fn analyzeCall(
80998099 return .unreachable_value;
81008100 }
81018101
8102 const result: Air.Inst.Ref = if (try sema.typeHasOnePossibleValue(sema.typeOf(maybe_opv))) |opv|
8103 .fromValue(opv)
8104 else
8105 maybe_opv;
8106
81028107 return result;
81038108 }
81048109
......@@ -8335,7 +8340,7 @@ fn analyzeCall(
83358340 break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, &inlining.merges, need_debug_scope);
83368341 };
83378342
8338 const result: Air.Inst.Ref = if (try sema.resolveValue(result_raw)) |result_val| r: {
8343 const maybe_opv: Air.Inst.Ref = if (try sema.resolveValue(result_raw)) |result_val| r: {
83398344 const val_resolved = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern());
83408345 break :r Air.internedToRef(val_resolved);
83418346 } else r: {
......@@ -8347,7 +8352,7 @@ fn analyzeCall(
83478352 };
83488353
83498354 if (block.isComptime()) {
8350 const result_val = (try sema.resolveValue(result)).?;
8355 const result_val = (try sema.resolveValue(maybe_opv)).?;
83518356 if (want_memoize and sema.allow_memoize and !result_val.canMutateComptimeVarState(zcu)) {
83528357 _ = try pt.intern(.{ .memoized_call = .{
83538358 .func = func_val.?.toIntern(),
......@@ -8359,10 +8364,10 @@ fn analyzeCall(
83598364 }
83608365
83618366 if (ensure_result_used) {
8362 try sema.ensureResultUsed(block, sema.typeOf(result), call_src);
8367 try sema.ensureResultUsed(block, sema.typeOf(maybe_opv), call_src);
83638368 }
83648369
8365 return result;
8370 return maybe_opv;
83668371}
83678372
83688373fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
......@@ -9065,10 +9070,14 @@ fn zirOptionalPayload(
90659070 };
90669071
90679072 if (try sema.resolveDefinedValue(block, src, operand)) |val| {
9068 return if (val.optionalValue(zcu)) |payload|
9069 Air.internedToRef(payload.toIntern())
9070 else
9071 sema.fail(block, src, "unable to unwrap null", .{});
9073 if (val.optionalValue(zcu)) |payload| return Air.internedToRef(payload.toIntern());
9074 if (block.isComptime()) return sema.fail(block, src, "unable to unwrap null", .{});
9075 if (safety_check and block.wantSafety()) {
9076 try sema.safetyPanic(block, src, .unwrap_null);
9077 } else {
9078 _ = try block.addNoOp(.unreach);
9079 }
9080 return .unreachable_value;
90729081 }
90739082
90749083 try sema.requireRuntimeBlock(block, src, null);
......@@ -36443,7 +36452,6 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
3644336452 .type_int_unsigned, // u0 handled above
3644436453 .type_pointer,
3644536454 .type_slice,
36446 .type_optional, // ?noreturn handled above
3644736455 .type_anyframe,
3644836456 .type_error_union,
3644936457 .type_anyerror_union,
......@@ -36655,6 +36663,15 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
3665536663
3665636664 else => unreachable,
3665736665 },
36666
36667 .type_optional => {
36668 const payload_ip = ip.indexToKey(ty.toIntern()).opt_type;
36669 // Although ?noreturn is handled above, the element type
36670 // can be effectively noreturn for example via an empty
36671 // enum or error set.
36672 if (ip.isNoReturn(payload_ip)) return try pt.nullValue(ty);
36673 return null;
36674 },
3665836675 },
3665936676 };
3666036677}
test/cases/compile_errors/optional_empty_error_set.zig created+13
......@@ -0,0 +1,13 @@
1export fn example() void {
2 comptime foo() catch |err| switch (err) {};
3}
4var x: ?error{} = null;
5fn foo() !void {
6 return x.?;
7}
8// error
9// backend=stage2
10// target=native
11//
12// :6:13: error: unable to unwrap null
13// :2:17: note: called at comptime here
test/cases/safety/optional_empty_error_set.zig created+22
......@@ -0,0 +1,22 @@
1const std = @import("std");
2
3pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, ra: ?usize) noreturn {
4 _ = stack_trace;
5 _ = ra;
6 if (std.mem.eql(u8, message, "attempt to use null value")) {
7 std.process.exit(0);
8 }
9 std.process.exit(1);
10}
11
12pub fn main() !void {
13 foo() catch |err| switch (err) {};
14 return error.TestFailed;
15}
16var x: ?error{} = null;
17fn foo() !void {
18 return x.?;
19}
20// run
21// backend=stage2,llvm
22// target=native