authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 14:27:00+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 14:37:28+00:00
log01546e68cd0d82ef78498a10649e6bc2937680da
tree19eb970a346b362eac55f7e73eb7ee3bfbf90cd7
parent3a4a7d2ca378862dd6b31678a143315a9e306f8c
signaturelock-open Commit is signed but in an unrecognized format.

compiler: handle switch rewrite review feedback


11 files changed, 127 insertions(+), 114 deletions(-)

doc/langref.html.in+1-1
......@@ -2595,7 +2595,7 @@ or
25952595
25962596 {#header_close#}
25972597
2598 {#header_open|Switching on errors#}
2598 {#header_open|Switching on Errors#}
25992599 <p>
26002600 When switching on errors, some special cases are allowed to simplify generic programming patterns:
26012601 </p>
doc/langref/test_switch_on_errors.zig+4-1
......@@ -12,7 +12,10 @@ test "unreachable else prong" {
1212 switch (openFile0()) {
1313 error.AccessDenied, error.FileNotFound => |e| return e,
1414 error.OutOfMemory => {},
15 else => unreachable, // technically unreachable, but will still compile!
15 // 'openFile0' cannot return any more errors, so an 'else' prong would be
16 // statically known to be unreachable. Nonetheless, in this case, adding
17 // one does not raise an "unreachable else prong" compile error:
18 else => unreachable,
1619 }
1720
1821 // Allowed unreachable else prongs are:
doc/langref/test_tagged_union.zig+4-1
......@@ -20,7 +20,10 @@ test "switch on tagged union" {
2020 }
2121
2222 switch (c) {
23 .ok => |_, tag| try expect(tag == .ok),
23 .ok => |_, tag| {
24 // Because we're in the '.ok' prong, 'tag' is compile-time known to be '.ok':
25 comptime std.debug.assert(tag == .ok);
26 },
2427 .not_ok => unreachable,
2528 }
2629}
src/Air/Liveness.zig+1-4
......@@ -176,10 +176,7 @@ pub fn analyze(zcu: *Zcu, air: Air, intern_pool: *InternPool) Allocator.Error!Li
176176 data.old_extra = a.extra;
177177 a.extra = .{};
178178 try analyzeBody(&a, .main_analysis, &data, main_body);
179 if (std.debug.runtime_safety and data.live_set.count() != 0) {
180 log.debug("instructions still in live set after analysis: {f}", .{fmtInstSet(&data.live_set)});
181 @panic("liveness analysis failed");
182 }
179 assert(data.live_set.count() == 0);
183180 }
184181
185182 return .{
src/Sema.zig+41-27
......@@ -12591,6 +12591,7 @@ fn resolveSwitchProng(
1259112591 sema.typeOf(operand.simple.by_val),
1259212592 capture_src,
1259312593 inline_case_capture,
12594 kind,
1259412595 );
1259512596 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
1259612597 break :inst tag_inst;
......@@ -12723,6 +12724,7 @@ fn analyzeSwitchProng(
1272312724 operand_ty,
1272412725 capture_src,
1272512726 inline_case_capture,
12727 kind,
1272612728 );
1272712729 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
1272812730 break :inst tag_inst;
......@@ -12743,6 +12745,7 @@ fn analyzeSwitchTagCapture(
1274312745 operand_ty: Type,
1274412746 capture_src: LazySrcLoc,
1274512747 inline_case_capture: Air.Inst.Ref,
12748 kind: SwitchProngKind,
1274612749) CompileError!Air.Inst.Ref {
1274712750 const pt = sema.pt;
1274812751 const zcu = pt.zcu;
......@@ -12760,6 +12763,10 @@ fn analyzeSwitchTagCapture(
1276012763 if (inline_case_capture != .none) {
1276112764 return inline_case_capture; // this already is the tag, it's what we're switching on!
1276212765 }
12766 switch (kind) {
12767 .has_ranges, .special => {},
12768 .item_refs => |refs| if (refs.len == 1) return refs[0],
12769 }
1276312770 const tag_ty = operand_ty.unionTagType(zcu).?;
1276412771 return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src);
1276512772}
......@@ -13150,10 +13157,12 @@ fn analyzeSwitchPayloadCapture(
1315013157 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
1315113158 },
1315213159 else => {
13153 // In this case the capture value is just the passed-through value
13154 // of the switch condition.
13160 // In this case the capture value is just the passed-through value of the
13161 // switch condition. It is comptime-known if there is only one item.
1315513162 if (capture_by_ref) {
1315613163 return operand_ptr;
13164 } else if (case_vals.len == 1) {
13165 return case_vals[0];
1315713166 } else {
1315813167 return operand_val;
1315913168 }
......@@ -31361,17 +31370,19 @@ fn resolvePtrIsNonErrVal(
3136131370 assert(ptr_ty.zigTypeTag(zcu) == .pointer);
3136231371 const child_ty = ptr_ty.childType(zcu);
3136331372
31364 const child_tag = child_ty.zigTypeTag(zcu);
31365 if (child_tag != .error_set and child_tag != .error_union) return .true;
31366 if (child_tag == .error_set) return .false;
31367 assert(child_tag == .error_union);
31373 if (try sema.resolveIsNonErrFromType(block, src, child_ty)) |res| {
31374 return res;
31375 }
31376 assert(child_ty.zigTypeTag(zcu) == .error_union);
3136831377
31369 if (try sema.resolveValue(operand)) |ptr_val| {
31370 if (ptr_val.isUndef(zcu)) return .undef_bool;
31371 if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |val| {
31372 return try sema.resolveIsNonErrVal(block, src, .fromValue(val));
31378 if (try sema.resolveValue(operand)) |eu_ptr_val| {
31379 if (eu_ptr_val.isUndef(zcu)) return .undef_bool;
31380 if (try sema.pointerDeref(block, src, eu_ptr_val, ptr_ty)) |err_union| {
31381 if (err_union.isUndef(zcu)) return .undef_bool;
31382 return .makeBool(err_union.getErrorName(zcu) == .none);
3137331383 }
3137431384 }
31385
3137531386 return null;
3137631387}
3137731388
......@@ -31380,11 +31391,30 @@ fn resolveIsNonErrVal(
3138031391 block: *Block,
3138131392 src: LazySrcLoc,
3138231393 operand: Air.Inst.Ref,
31394) CompileError!?Value {
31395 const zcu = sema.pt.zcu;
31396 if (try sema.resolveIsNonErrFromType(block, src, sema.typeOf(operand))) |res| {
31397 return res;
31398 }
31399 assert(sema.typeOf(operand).zigTypeTag(zcu) == .error_union);
31400
31401 if (try sema.resolveValue(operand)) |err_union| {
31402 if (err_union.isUndef(zcu)) return .undef_bool;
31403 return .makeBool(err_union.getErrorName(zcu) == .none);
31404 }
31405
31406 return null;
31407}
31408
31409fn resolveIsNonErrFromType(
31410 sema: *Sema,
31411 block: *Block,
31412 src: LazySrcLoc,
31413 operand_ty: Type,
3138331414) CompileError!?Value {
3138431415 const pt = sema.pt;
3138531416 const zcu = pt.zcu;
3138631417 const ip = &zcu.intern_pool;
31387 const operand_ty = sema.typeOf(operand);
3138831418 const ot = operand_ty.zigTypeTag(zcu);
3138931419 if (ot != .error_set and ot != .error_union) return .true;
3139031420 if (ot == .error_set) return .false;
......@@ -31395,15 +31425,6 @@ fn resolveIsNonErrVal(
3139531425 return .false;
3139631426 }
3139731427
31398 if (operand == .undef) {
31399 return .undef_bool;
31400 } else if (@intFromEnum(operand) < InternPool.static_len) {
31401 // None of the ref tags can be errors.
31402 return .true;
31403 }
31404
31405 const maybe_operand_val = try sema.resolveValue(operand);
31406
3140731428 // exception if the error union error set is known to be empty,
3140831429 // we allow the comparison but always make it comptime-known.
3140931430 const set_ty = ip.errorUnionSet(operand_ty.toIntern());
......@@ -31419,9 +31440,6 @@ fn resolveIsNonErrVal(
3141931440 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
3142031441 }
3142131442
31422 if (maybe_operand_val != null) break :blk;
31423
31424 // Try to avoid resolving inferred error set if possible.
3142531443 if (ies.errors.count() != 0) return null;
3142631444 switch (ies.resolved) {
3142731445 .anyerror_type => return null,
......@@ -31450,7 +31468,6 @@ fn resolveIsNonErrVal(
3145031468 .none => {},
3145131469 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
3145231470 }
31453 if (maybe_operand_val != null) break :blk;
3145431471 if (sema.fn_ret_ty_ies) |ies| {
3145531472 if (ies.func == func_index) {
3145631473 // Try to avoid resolving inferred error set if possible.
......@@ -31479,9 +31496,6 @@ fn resolveIsNonErrVal(
3147931496 },
3148031497 }
3148131498
31482 if (maybe_operand_val) |err_union| {
31483 return if (err_union.isUndef(zcu)) .undef_bool else if (err_union.getErrorName(zcu) == .none) .true else .false;
31484 }
3148531499 return null;
3148631500}
3148731501
src/Zcu.zig+1-10
......@@ -2221,16 +2221,7 @@ pub const SrcLoc = struct {
22212221 continue;
22222222 }
22232223 return tree.nodeToSpan(item_node);
2224 } else {
2225 for (case.ast.values) |item_node| {
2226 const item_span = tree.nodeToSpan(item_node);
2227 std.debug.print("{s}\n", .{tree.source[item_span.start..item_span.end]});
2228 }
2229 std.debug.print("want_case_idx={any}\n", .{want_case_idx});
2230 std.debug.print("want_item_idx={any}\n", .{want_item_idx});
2231 unreachable;
2232 }
2233 // } else unreachable;
2224 } else unreachable;
22342225 },
22352226 .range => {
22362227 var range_i: u32 = 0;
test/behavior/for.zig+1-1
......@@ -525,7 +525,7 @@ test "for loop 0 length range" {
525525 }
526526}
527527
528test "labeled break from else prong" {
528test "labeled break from else" {
529529 const S = struct {
530530 fn doTheTest(x: u32) !void {
531531 var y: u32 = 0;
test/behavior/switch.zig+55-56
......@@ -1128,20 +1128,17 @@ test "decl literals as switch cases" {
11281128
11291129 const foo: @This() = @enumFromInt(0xa);
11301130
1131 fn doTheTest() !void {
1132 var e: @This() = .foo;
1133 _ = &e;
1134 const ok = switch (e) {
1135 .bar => false,
1136 .foo => true,
1137 else => false,
1138 };
1139 try expect(ok);
1131 fn doTheTest(e: @This()) !void {
1132 switch (e) {
1133 .bar => return error.TestFailed,
1134 .foo => {},
1135 else => return error.TestFailed,
1136 }
11401137 }
11411138 };
11421139
1143 try E.doTheTest();
1144 try comptime E.doTheTest();
1140 try E.doTheTest(.foo);
1141 try comptime E.doTheTest(.foo);
11451142}
11461143
11471144// TODO audit after #15909 and/or #19855 are decided/implemented
......@@ -1152,32 +1149,30 @@ test "switch with uninstantiable union fields" {
11521149 b: noreturn,
11531150 c: error{},
11541151
1155 fn doTheTest() !void {
1156 var u: @This() = .ok;
1157 _ = &u;
1158 try expect(switch (u) {
1159 .ok => true,
1152 fn doTheTest(u: @This()) void {
1153 switch (u) {
1154 .ok => {},
11601155 .a => comptime unreachable,
11611156 .b => comptime unreachable,
11621157 .c => comptime unreachable,
1163 });
1164 try expect(switch (u) {
1165 .ok => true,
1158 }
1159 switch (u) {
1160 .ok => {},
11661161 .a, .b, .c => comptime unreachable,
1167 });
1168 try expect(switch (u) {
1169 .ok => true,
1162 }
1163 switch (u) {
1164 .ok => {},
11701165 else => comptime unreachable,
1171 });
1172 try expect(switch (u) {
1166 }
1167 switch (u) {
11731168 .a => comptime unreachable,
1174 .ok, .b, .c => true,
1175 });
1169 .ok, .b, .c => {},
1170 }
11761171 }
11771172 };
11781173
1179 try U.doTheTest();
1180 try comptime U.doTheTest();
1174 U.doTheTest(.ok);
1175 comptime U.doTheTest(.ok);
11811176}
11821177
11831178test "switch with tag capture" {
......@@ -1196,8 +1191,8 @@ test "switch with tag capture" {
11961191 fn doTheSwitch(u: @This()) !void {
11971192 switch (u) {
11981193 .a => |nothing, tag| {
1199 try expect(nothing == {});
1200 try expect(tag == .a);
1194 comptime assert(nothing == {});
1195 comptime assert(tag == .a);
12011196 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
12021197 },
12031198 .b, .d => |_, tag| {
......@@ -1216,13 +1211,13 @@ test "switch with tag capture" {
12161211 }
12171212 switch (u) {
12181213 inline .a, .b, .c => |payload, tag| {
1219 if (@TypeOf(payload) == void) try expect(tag == .a);
1220 if (@TypeOf(payload) == i32) try expect(tag == .b);
1221 if (@TypeOf(payload) == u8) try expect(tag == .c);
1214 if (@TypeOf(payload) == void) comptime assert(tag == .a);
1215 if (@TypeOf(payload) == i32) comptime assert(tag == .b);
1216 if (@TypeOf(payload) == u8) comptime assert(tag == .c);
12221217 },
12231218 inline else => |payload, tag| {
1224 if (@TypeOf(payload) == i32) try expect(tag == .d);
1225 try expect(tag != .e);
1219 if (@TypeOf(payload) == i32) comptime assert(tag == .d);
1220 comptime assert(tag != .e);
12261221 },
12271222 }
12281223 }
......@@ -1232,7 +1227,7 @@ test "switch with tag capture" {
12321227 try comptime U.doTheTest();
12331228}
12341229
1235test "switch with advanced prong items" {
1230test "switch with complex item expressions" {
12361231 const S = struct {
12371232 fn doTheTest() !void {
12381233 try doTheSwitch(2000, 20);
......@@ -1278,19 +1273,11 @@ test "switch with advanced prong items" {
12781273}
12791274
12801275test "switch evaluation order" {
1281 const eval = comptime eval: {
1282 var eval = false;
1283 const eu: anyerror!u32 = 0;
1284 _ = eu catch |err| switch (err) {
1285 blk: {
1286 eval = true;
1287 break :blk error.MyError;
1288 } => {},
1289 else => unreachable,
1290 };
1291 break :eval eval;
1276 const eu: anyerror!u32 = 0;
1277 _ = eu catch |err| switch (err) {
1278 if (true) @compileError("unreachable") => unreachable,
1279 else => unreachable,
12921280 };
1293 try comptime expect(!eval);
12941281}
12951282
12961283test "switch resolves lazy values correctly" {
......@@ -1298,13 +1285,25 @@ test "switch resolves lazy values correctly" {
12981285 a: u16,
12991286 b: i16,
13001287 };
1301 const ok1 = switch (@sizeOf(S)) {
1302 4 => true,
1303 else => false,
1304 };
1305 const ok2 = switch (@sizeOf(S)) {
1306 4 => true,
1307 else => false,
1288 switch (@sizeOf(S)) {
1289 4 => {},
1290 else => comptime unreachable,
1291 }
1292}
1293
1294test "single-item prong in switch on enum has comptime-known capture" {
1295 const E = enum {
1296 a,
1297 b,
1298 c,
1299 fn doTheTest(e: @This()) !void {
1300 switch (e) {
1301 .a => |tag| comptime assert(tag == .a),
1302 .b => return error.TestFailed,
1303 .c => return error.TestFailed,
1304 }
1305 }
13081306 };
1309 try comptime expect(ok1 == ok2);
1307 try E.doTheTest(.a);
1308 try comptime E.doTheTest(.a);
13101309}
test/behavior/switch_loop.zig+9-8
......@@ -1,5 +1,6 @@
11const builtin = @import("builtin");
22const std = @import("std");
3const assert = std.debug.assert;
34const expect = std.testing.expect;
45
56test "simple switch loop" {
......@@ -340,7 +341,7 @@ test "switch loop with single catch-all prong" {
340341 continue :label .{ .b = 456 };
341342 },
342343 };
343 try expect(ok);
344 comptime assert(ok);
344345 }
345346 };
346347 try S.doTheTest();
......@@ -411,8 +412,8 @@ test "switch loop with tag capture" {
411412 fn doTheSwitch(u: @This()) !void {
412413 const ok1 = label: switch (u) {
413414 .a => |nothing, tag| {
414 try expect(nothing == {});
415 try expect(tag == .a);
415 comptime assert(nothing == {});
416 comptime assert(tag == .a);
416417 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
417418 continue :label .{ .d = 456 };
418419 },
......@@ -438,21 +439,21 @@ test "switch loop with tag capture" {
438439 const ok2 = label: switch (u) {
439440 inline .a, .b, .c => |payload, tag| {
440441 if (@TypeOf(payload) == void) {
441 try expect(tag == .a);
442 comptime assert(tag == .a);
442443 continue :label .{ .b = 456 };
443444 }
444445 if (@TypeOf(payload) == i32) {
445 try expect(tag == .b);
446 comptime assert(tag == .b);
446447 continue :label .{ .d = payload };
447448 }
448449 if (@TypeOf(payload) == u8) {
449 try expect(tag == .c);
450 comptime assert(tag == .c);
450451 continue :label .{ .d = payload };
451452 }
452453 },
453454 inline else => |payload, tag| {
454 if (@TypeOf(payload) == i32) try expect(tag == .d);
455 try expect(tag != .e);
455 if (@TypeOf(payload) == i32) comptime assert(tag == .d);
456 comptime assert(tag != .e);
456457 if (payload == 0) break :label false;
457458 break :label true;
458459 },
test/behavior/switch_on_captured_error.zig+6-1
......@@ -17,7 +17,9 @@ test "switch on error union catch capture" {
1717 try testElse();
1818 try testCapture();
1919 try testInline();
20 try testEmptyErrSet();
2021 try testUnreachableElseProng();
22 try testErrNotInSet();
2123 try testAddressOf();
2224 }
2325
......@@ -384,8 +386,11 @@ test "switch on error union if else capture" {
384386 try testCapturePtr();
385387 try testInline();
386388 try testInlinePtr();
389 try testEmptyErrSet();
390 try testEmptyErrSetPtr();
387391 try testUnreachableElseProng();
388392 try testUnreachableElseProngPtr();
393 try testErrNotInSet();
389394 try testAddressOf();
390395 }
391396
......@@ -835,7 +840,7 @@ test "switch on error union if else capture" {
835840 var a: error{}!u64 = 0;
836841 _ = &a;
837842 const b = if (a) |*x| x.* else |err| switch (err) {
838 error.undefined => @compileError("unreachable"),
843 undefined => @compileError("unreachable"),
839844 };
840845 try expectEqual(@as(u64, 0), b);
841846 }
test/behavior/while.zig+4-4
......@@ -400,12 +400,12 @@ test "breaking from a loop in an if statement" {
400400 _ = opt;
401401}
402402
403test "labeled break from else prong" {
403test "labeled break from else" {
404404 const S = struct {
405405 fn doTheTest(x: u32) !void {
406 var y: u32 = 0;
407 const ok = label: while (y < x) : (y += 1) {
408 if (y == 10) break :label false;
406 const arr: []const u32 = &.{ 1, 3, 10 };
407 const ok = label: for (arr) |y| {
408 if (y == x) break :label false;
409409 } else {
410410 break :label true;
411411 };