diff --git a/doc/langref.html.in b/doc/langref.html.in index 07a5b39f2f..49cc385200 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2595,7 +2595,7 @@ or {#header_close#} - {#header_open|Switching on errors#} + {#header_open|Switching on Errors#}
When switching on errors, some special cases are allowed to simplify generic programming patterns:
diff --git a/doc/langref/test_switch_on_errors.zig b/doc/langref/test_switch_on_errors.zig index 7fe534e7a3..cb1a8249e3 100644 --- a/doc/langref/test_switch_on_errors.zig +++ b/doc/langref/test_switch_on_errors.zig @@ -12,7 +12,10 @@ test "unreachable else prong" { switch (openFile0()) { error.AccessDenied, error.FileNotFound => |e| return e, error.OutOfMemory => {}, - else => unreachable, // technically unreachable, but will still compile! + // 'openFile0' cannot return any more errors, so an 'else' prong would be + // statically known to be unreachable. Nonetheless, in this case, adding + // one does not raise an "unreachable else prong" compile error: + else => unreachable, } // Allowed unreachable else prongs are: diff --git a/doc/langref/test_tagged_union.zig b/doc/langref/test_tagged_union.zig index 58df9dc38d..d118d7e6bd 100644 --- a/doc/langref/test_tagged_union.zig +++ b/doc/langref/test_tagged_union.zig @@ -20,7 +20,10 @@ test "switch on tagged union" { } switch (c) { - .ok => |_, tag| try expect(tag == .ok), + .ok => |_, tag| { + // Because we're in the '.ok' prong, 'tag' is compile-time known to be '.ok': + comptime std.debug.assert(tag == .ok); + }, .not_ok => unreachable, } } diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig index 051472a97a..9b722973a6 100644 --- a/src/Air/Liveness.zig +++ b/src/Air/Liveness.zig @@ -176,10 +176,7 @@ pub fn analyze(zcu: *Zcu, air: Air, intern_pool: *InternPool) Allocator.Error!Li data.old_extra = a.extra; a.extra = .{}; try analyzeBody(&a, .main_analysis, &data, main_body); - if (std.debug.runtime_safety and data.live_set.count() != 0) { - log.debug("instructions still in live set after analysis: {f}", .{fmtInstSet(&data.live_set)}); - @panic("liveness analysis failed"); - } + assert(data.live_set.count() == 0); } return .{ diff --git a/src/Sema.zig b/src/Sema.zig index ceb4077325..8a33a8136c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12591,6 +12591,7 @@ fn resolveSwitchProng( sema.typeOf(operand.simple.by_val), capture_src, inline_case_capture, + kind, ); sema.inst_map.putAssumeCapacity(tag_inst, tag_ref); break :inst tag_inst; @@ -12723,6 +12724,7 @@ fn analyzeSwitchProng( operand_ty, capture_src, inline_case_capture, + kind, ); sema.inst_map.putAssumeCapacity(tag_inst, tag_ref); break :inst tag_inst; @@ -12743,6 +12745,7 @@ fn analyzeSwitchTagCapture( operand_ty: Type, capture_src: LazySrcLoc, inline_case_capture: Air.Inst.Ref, + kind: SwitchProngKind, ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; @@ -12760,6 +12763,10 @@ fn analyzeSwitchTagCapture( if (inline_case_capture != .none) { return inline_case_capture; // this already is the tag, it's what we're switching on! } + switch (kind) { + .has_ranges, .special => {}, + .item_refs => |refs| if (refs.len == 1) return refs[0], + } const tag_ty = operand_ty.unionTagType(zcu).?; return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src); } @@ -13150,10 +13157,12 @@ fn analyzeSwitchPayloadCapture( return sema.bitCast(case_block, error_ty, operand_val, operand_src, null); }, else => { - // In this case the capture value is just the passed-through value - // of the switch condition. + // In this case the capture value is just the passed-through value of the + // switch condition. It is comptime-known if there is only one item. if (capture_by_ref) { return operand_ptr; + } else if (case_vals.len == 1) { + return case_vals[0]; } else { return operand_val; } @@ -31361,17 +31370,19 @@ fn resolvePtrIsNonErrVal( assert(ptr_ty.zigTypeTag(zcu) == .pointer); const child_ty = ptr_ty.childType(zcu); - const child_tag = child_ty.zigTypeTag(zcu); - if (child_tag != .error_set and child_tag != .error_union) return .true; - if (child_tag == .error_set) return .false; - assert(child_tag == .error_union); + if (try sema.resolveIsNonErrFromType(block, src, child_ty)) |res| { + return res; + } + assert(child_ty.zigTypeTag(zcu) == .error_union); - if (try sema.resolveValue(operand)) |ptr_val| { - if (ptr_val.isUndef(zcu)) return .undef_bool; - if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |val| { - return try sema.resolveIsNonErrVal(block, src, .fromValue(val)); + if (try sema.resolveValue(operand)) |eu_ptr_val| { + if (eu_ptr_val.isUndef(zcu)) return .undef_bool; + if (try sema.pointerDeref(block, src, eu_ptr_val, ptr_ty)) |err_union| { + if (err_union.isUndef(zcu)) return .undef_bool; + return .makeBool(err_union.getErrorName(zcu) == .none); } } + return null; } @@ -31380,11 +31391,30 @@ fn resolveIsNonErrVal( block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref, +) CompileError!?Value { + const zcu = sema.pt.zcu; + if (try sema.resolveIsNonErrFromType(block, src, sema.typeOf(operand))) |res| { + return res; + } + assert(sema.typeOf(operand).zigTypeTag(zcu) == .error_union); + + if (try sema.resolveValue(operand)) |err_union| { + if (err_union.isUndef(zcu)) return .undef_bool; + return .makeBool(err_union.getErrorName(zcu) == .none); + } + + return null; +} + +fn resolveIsNonErrFromType( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + operand_ty: Type, ) CompileError!?Value { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - const operand_ty = sema.typeOf(operand); const ot = operand_ty.zigTypeTag(zcu); if (ot != .error_set and ot != .error_union) return .true; if (ot == .error_set) return .false; @@ -31395,15 +31425,6 @@ fn resolveIsNonErrVal( return .false; } - if (operand == .undef) { - return .undef_bool; - } else if (@intFromEnum(operand) < InternPool.static_len) { - // None of the ref tags can be errors. - return .true; - } - - const maybe_operand_val = try sema.resolveValue(operand); - // exception if the error union error set is known to be empty, // we allow the comparison but always make it comptime-known. const set_ty = ip.errorUnionSet(operand_ty.toIntern()); @@ -31419,9 +31440,6 @@ fn resolveIsNonErrVal( else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk, } - if (maybe_operand_val != null) break :blk; - - // Try to avoid resolving inferred error set if possible. if (ies.errors.count() != 0) return null; switch (ies.resolved) { .anyerror_type => return null, @@ -31450,7 +31468,6 @@ fn resolveIsNonErrVal( .none => {}, else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk, } - if (maybe_operand_val != null) break :blk; if (sema.fn_ret_ty_ies) |ies| { if (ies.func == func_index) { // Try to avoid resolving inferred error set if possible. @@ -31479,9 +31496,6 @@ fn resolveIsNonErrVal( }, } - if (maybe_operand_val) |err_union| { - return if (err_union.isUndef(zcu)) .undef_bool else if (err_union.getErrorName(zcu) == .none) .true else .false; - } return null; } diff --git a/src/Zcu.zig b/src/Zcu.zig index 1de99157ab..d1e20c8551 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -2221,16 +2221,7 @@ pub const SrcLoc = struct { continue; } return tree.nodeToSpan(item_node); - } else { - for (case.ast.values) |item_node| { - const item_span = tree.nodeToSpan(item_node); - std.debug.print("{s}\n", .{tree.source[item_span.start..item_span.end]}); - } - std.debug.print("want_case_idx={any}\n", .{want_case_idx}); - std.debug.print("want_item_idx={any}\n", .{want_item_idx}); - unreachable; - } - // } else unreachable; + } else unreachable; }, .range => { var range_i: u32 = 0; diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 0361c2d19d..3f12767c9f 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -525,7 +525,7 @@ test "for loop 0 length range" { } } -test "labeled break from else prong" { +test "labeled break from else" { const S = struct { fn doTheTest(x: u32) !void { var y: u32 = 0; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 1e1cfdd3a9..483194e00b 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -1128,20 +1128,17 @@ test "decl literals as switch cases" { const foo: @This() = @enumFromInt(0xa); - fn doTheTest() !void { - var e: @This() = .foo; - _ = &e; - const ok = switch (e) { - .bar => false, - .foo => true, - else => false, - }; - try expect(ok); + fn doTheTest(e: @This()) !void { + switch (e) { + .bar => return error.TestFailed, + .foo => {}, + else => return error.TestFailed, + } } }; - try E.doTheTest(); - try comptime E.doTheTest(); + try E.doTheTest(.foo); + try comptime E.doTheTest(.foo); } // TODO audit after #15909 and/or #19855 are decided/implemented @@ -1152,32 +1149,30 @@ test "switch with uninstantiable union fields" { b: noreturn, c: error{}, - fn doTheTest() !void { - var u: @This() = .ok; - _ = &u; - try expect(switch (u) { - .ok => true, + fn doTheTest(u: @This()) void { + switch (u) { + .ok => {}, .a => comptime unreachable, .b => comptime unreachable, .c => comptime unreachable, - }); - try expect(switch (u) { - .ok => true, + } + switch (u) { + .ok => {}, .a, .b, .c => comptime unreachable, - }); - try expect(switch (u) { - .ok => true, + } + switch (u) { + .ok => {}, else => comptime unreachable, - }); - try expect(switch (u) { + } + switch (u) { .a => comptime unreachable, - .ok, .b, .c => true, - }); + .ok, .b, .c => {}, + } } }; - try U.doTheTest(); - try comptime U.doTheTest(); + U.doTheTest(.ok); + comptime U.doTheTest(.ok); } test "switch with tag capture" { @@ -1196,8 +1191,8 @@ test "switch with tag capture" { fn doTheSwitch(u: @This()) !void { switch (u) { .a => |nothing, tag| { - try expect(nothing == {}); - try expect(tag == .a); + comptime assert(nothing == {}); + comptime assert(tag == .a); try expect(@intFromEnum(tag) == @intFromEnum(@This().a)); }, .b, .d => |_, tag| { @@ -1216,13 +1211,13 @@ test "switch with tag capture" { } switch (u) { inline .a, .b, .c => |payload, tag| { - if (@TypeOf(payload) == void) try expect(tag == .a); - if (@TypeOf(payload) == i32) try expect(tag == .b); - if (@TypeOf(payload) == u8) try expect(tag == .c); + if (@TypeOf(payload) == void) comptime assert(tag == .a); + if (@TypeOf(payload) == i32) comptime assert(tag == .b); + if (@TypeOf(payload) == u8) comptime assert(tag == .c); }, inline else => |payload, tag| { - if (@TypeOf(payload) == i32) try expect(tag == .d); - try expect(tag != .e); + if (@TypeOf(payload) == i32) comptime assert(tag == .d); + comptime assert(tag != .e); }, } } @@ -1232,7 +1227,7 @@ test "switch with tag capture" { try comptime U.doTheTest(); } -test "switch with advanced prong items" { +test "switch with complex item expressions" { const S = struct { fn doTheTest() !void { try doTheSwitch(2000, 20); @@ -1278,19 +1273,11 @@ test "switch with advanced prong items" { } test "switch evaluation order" { - const eval = comptime eval: { - var eval = false; - const eu: anyerror!u32 = 0; - _ = eu catch |err| switch (err) { - blk: { - eval = true; - break :blk error.MyError; - } => {}, - else => unreachable, - }; - break :eval eval; + const eu: anyerror!u32 = 0; + _ = eu catch |err| switch (err) { + if (true) @compileError("unreachable") => unreachable, + else => unreachable, }; - try comptime expect(!eval); } test "switch resolves lazy values correctly" { @@ -1298,13 +1285,25 @@ test "switch resolves lazy values correctly" { a: u16, b: i16, }; - const ok1 = switch (@sizeOf(S)) { - 4 => true, - else => false, - }; - const ok2 = switch (@sizeOf(S)) { - 4 => true, - else => false, - }; - try comptime expect(ok1 == ok2); + switch (@sizeOf(S)) { + 4 => {}, + else => comptime unreachable, + } +} + +test "single-item prong in switch on enum has comptime-known capture" { + const E = enum { + a, + b, + c, + fn doTheTest(e: @This()) !void { + switch (e) { + .a => |tag| comptime assert(tag == .a), + .b => return error.TestFailed, + .c => return error.TestFailed, + } + } + }; + try E.doTheTest(.a); + try comptime E.doTheTest(.a); } diff --git a/test/behavior/switch_loop.zig b/test/behavior/switch_loop.zig index 3da7838fbc..bbf9c86d50 100644 --- a/test/behavior/switch_loop.zig +++ b/test/behavior/switch_loop.zig @@ -1,5 +1,6 @@ const builtin = @import("builtin"); const std = @import("std"); +const assert = std.debug.assert; const expect = std.testing.expect; test "simple switch loop" { @@ -340,7 +341,7 @@ test "switch loop with single catch-all prong" { continue :label .{ .b = 456 }; }, }; - try expect(ok); + comptime assert(ok); } }; try S.doTheTest(); @@ -411,8 +412,8 @@ test "switch loop with tag capture" { fn doTheSwitch(u: @This()) !void { const ok1 = label: switch (u) { .a => |nothing, tag| { - try expect(nothing == {}); - try expect(tag == .a); + comptime assert(nothing == {}); + comptime assert(tag == .a); try expect(@intFromEnum(tag) == @intFromEnum(@This().a)); continue :label .{ .d = 456 }; }, @@ -438,21 +439,21 @@ test "switch loop with tag capture" { const ok2 = label: switch (u) { inline .a, .b, .c => |payload, tag| { if (@TypeOf(payload) == void) { - try expect(tag == .a); + comptime assert(tag == .a); continue :label .{ .b = 456 }; } if (@TypeOf(payload) == i32) { - try expect(tag == .b); + comptime assert(tag == .b); continue :label .{ .d = payload }; } if (@TypeOf(payload) == u8) { - try expect(tag == .c); + comptime assert(tag == .c); continue :label .{ .d = payload }; } }, inline else => |payload, tag| { - if (@TypeOf(payload) == i32) try expect(tag == .d); - try expect(tag != .e); + if (@TypeOf(payload) == i32) comptime assert(tag == .d); + comptime assert(tag != .e); if (payload == 0) break :label false; break :label true; }, diff --git a/test/behavior/switch_on_captured_error.zig b/test/behavior/switch_on_captured_error.zig index cc5172a32b..c4353cd5df 100644 --- a/test/behavior/switch_on_captured_error.zig +++ b/test/behavior/switch_on_captured_error.zig @@ -17,7 +17,9 @@ test "switch on error union catch capture" { try testElse(); try testCapture(); try testInline(); + try testEmptyErrSet(); try testUnreachableElseProng(); + try testErrNotInSet(); try testAddressOf(); } @@ -384,8 +386,11 @@ test "switch on error union if else capture" { try testCapturePtr(); try testInline(); try testInlinePtr(); + try testEmptyErrSet(); + try testEmptyErrSetPtr(); try testUnreachableElseProng(); try testUnreachableElseProngPtr(); + try testErrNotInSet(); try testAddressOf(); } @@ -835,7 +840,7 @@ test "switch on error union if else capture" { var a: error{}!u64 = 0; _ = &a; const b = if (a) |*x| x.* else |err| switch (err) { - error.undefined => @compileError("unreachable"), + undefined => @compileError("unreachable"), }; try expectEqual(@as(u64, 0), b); } diff --git a/test/behavior/while.zig b/test/behavior/while.zig index 9b5fe1fb84..2854f8a8d4 100644 --- a/test/behavior/while.zig +++ b/test/behavior/while.zig @@ -400,12 +400,12 @@ test "breaking from a loop in an if statement" { _ = opt; } -test "labeled break from else prong" { +test "labeled break from else" { const S = struct { fn doTheTest(x: u32) !void { - var y: u32 = 0; - const ok = label: while (y < x) : (y += 1) { - if (y == 10) break :label false; + const arr: []const u32 = &.{ 1, 3, 10 }; + const ok = label: for (arr) |y| { + if (y == x) break :label false; } else { break :label true; };