From 312c17be163067592346133ddf78134f85e9cb68 Mon Sep 17 00:00:00 2001 From: Jackson Wambolt Date: Mon, 1 Sep 2025 18:37:17 -0500 Subject: [PATCH 1/3] Sema: add another `!noreturn` behavior test case --- test/behavior/error.zig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 4ce94bb43b9f..b1778397c8a3 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -819,6 +819,25 @@ test "error union of noreturn used with catch" { try expect(err == error.OtherFailure); } +test "error union of noreturn used with catch + switch" { + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; + + NoReturn.a = 64; + const caught: bool = NoReturn.loop() catch |err| switch (err) { + error.GenericFailure => true, + }; + try expect(caught); + + NoReturn.a = 64; + const err = NoReturn.loop() catch |err| switch (err) { + else => |e| e, + }; + try expect(err == error.GenericFailure); +} + test "alignment of wrapping an error union payload" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO From 66d9c37a4ea89ffa0ca49f158a1f758b8b1e746a Mon Sep 17 00:00:00 2001 From: Jackson Wambolt Date: Sun, 7 Sep 2025 16:18:01 -0500 Subject: [PATCH 2/3] Sema: convert uses of `ArrayListUnmanaged` --- src/Sema.zig | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 51b93fb30830..d85cc5bb1a9d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -46,7 +46,7 @@ gpa: Allocator, arena: Allocator, code: Zir, air_instructions: std.MultiArrayList(Air.Inst) = .{}, -air_extra: std.ArrayListUnmanaged(u32) = .empty, +air_extra: std.ArrayList(u32) = .empty, /// Maps ZIR to AIR. inst_map: InstMap = .{}, /// The "owner" of a `Sema` represents the root "thing" that is being analyzed. @@ -111,11 +111,11 @@ maybe_comptime_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, MaybeComptimeAll /// stored as elements of this array. /// Pointers to such memory are represented via an index into this array. /// Backed by gpa. -comptime_allocs: std.ArrayListUnmanaged(ComptimeAlloc) = .empty, +comptime_allocs: std.ArrayList(ComptimeAlloc) = .empty, /// A list of exports performed by this analysis. After this `Sema` terminates, /// these are flushed to `Zcu.single_exports` or `Zcu.multi_exports`. -exports: std.ArrayListUnmanaged(Zcu.Export) = .empty, +exports: std.ArrayList(Zcu.Export) = .empty, /// All references registered so far by this `Sema`. This is a temporary duplicate /// of data stored in `Zcu.all_references`. It exists to avoid adding references to @@ -343,7 +343,7 @@ pub const Block = struct { /// The namespace to use for lookups from this source block namespace: InternPool.NamespaceIndex, /// The AIR instructions generated for this block. - instructions: std.ArrayListUnmanaged(Air.Inst.Index), + instructions: std.ArrayList(Air.Inst.Index), // `param` instructions are collected here to be used by the `func` instruction. /// When doing a generic function instantiation, this array collects a type /// for each *runtime-known* parameter. This array corresponds to the instance @@ -475,23 +475,23 @@ pub const Block = struct { block_inst: Air.Inst.Index, /// Separate array list from break_inst_list so that it can be passed directly /// to resolvePeerTypes. - results: std.ArrayListUnmanaged(Air.Inst.Ref), + results: std.ArrayList(Air.Inst.Ref), /// Keeps track of the break instructions so that the operand can be replaced /// if we need to add type coercion at the end of block analysis. /// Same indexes, capacity, length as `results`. - br_list: std.ArrayListUnmanaged(Air.Inst.Index), + br_list: std.ArrayList(Air.Inst.Index), /// Keeps the source location of the rhs operand of the break instruction, /// to enable more precise compile errors. /// Same indexes, capacity, length as `results`. - src_locs: std.ArrayListUnmanaged(?LazySrcLoc), + src_locs: std.ArrayList(?LazySrcLoc), /// Most blocks do not utilize this field. When it is used, its use is /// contextual. The possible uses are as follows: /// * for a `switch_block[_ref]`, this refers to dummy `br` instructions /// which correspond to `switch_continue` ZIR. The switch logic will /// rewrite these to appropriate AIR switch dispatches. - extra_insts: std.ArrayListUnmanaged(Air.Inst.Index) = .empty, + extra_insts: std.ArrayList(Air.Inst.Index) = .empty, /// Same indexes, capacity, length as `extra_insts`. - extra_src_locs: std.ArrayListUnmanaged(LazySrcLoc) = .empty, + extra_src_locs: std.ArrayList(LazySrcLoc) = .empty, pub fn deinit(merges: *@This(), allocator: Allocator) void { merges.results.deinit(allocator); @@ -985,7 +985,7 @@ const InferredAlloc = struct { /// is known. These should be rewritten to perform any required coercions /// when the type is resolved. /// Allocated from `sema.arena`. - prongs: std.ArrayListUnmanaged(Air.Inst.Index) = .empty, + prongs: std.ArrayList(Air.Inst.Index) = .empty, }; pub fn deinit(sema: *Sema) void { @@ -7502,8 +7502,8 @@ fn analyzeCall( // This may be an overestimate, but it's definitely sufficient. const max_runtime_args = args_info.count() - @popCount(func_ty_info.comptime_bits); - var runtime_args: std.ArrayListUnmanaged(Air.Inst.Ref) = try .initCapacity(arena, max_runtime_args); - var runtime_param_tys: std.ArrayListUnmanaged(InternPool.Index) = try .initCapacity(arena, max_runtime_args); + var runtime_args: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, max_runtime_args); + var runtime_param_tys: std.ArrayList(InternPool.Index) = try .initCapacity(arena, max_runtime_args); const comptime_args = try arena.alloc(InternPool.Index, args_info.count()); @@ -11000,7 +11000,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp break :blk err_capture_inst; } else undefined; - var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); + var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); defer case_vals.deinit(gpa); const NonError = struct { @@ -11383,7 +11383,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r break :blk tag_capture_inst; } else undefined; - var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); + var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); defer case_vals.deinit(gpa); var single_absorbed_item: Zir.Inst.Ref = .none; @@ -12037,8 +12037,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r } var extra_case_vals: struct { - items: std.ArrayListUnmanaged(Air.Inst.Ref), - ranges: std.ArrayListUnmanaged([2]Air.Inst.Ref), + items: std.ArrayList(Air.Inst.Ref), + ranges: std.ArrayList([2]Air.Inst.Ref), } = .{ .items = .empty, .ranges = .empty }; defer { extra_case_vals.items.deinit(gpa); @@ -12230,7 +12230,7 @@ fn analyzeSwitchRuntimeBlock( operand: Air.Inst.Ref, operand_ty: Type, operand_src: LazySrcLoc, - case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), + case_vals: std.ArrayList(Air.Inst.Ref), else_prong: SpecialProng, scalar_cases_len: usize, multi_cases_len: usize, @@ -12262,10 +12262,10 @@ fn analyzeSwitchRuntimeBlock( const estimated_cases_extra = (scalar_cases_len + multi_cases_len) * @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + 2; - var cases_extra = try std.ArrayListUnmanaged(u32).initCapacity(gpa, estimated_cases_extra); + var cases_extra: std.ArrayList(u32) = try .initCapacity(gpa, estimated_cases_extra); defer cases_extra.deinit(gpa); - var branch_hints = try std.ArrayListUnmanaged(std.builtin.BranchHint).initCapacity(gpa, scalar_cases_len); + var branch_hints: std.ArrayList(std.builtin.BranchHint) = try .initCapacity(gpa, scalar_cases_len); defer branch_hints.deinit(gpa); var case_block = child_block.makeSubBlock(); @@ -12915,7 +12915,7 @@ fn resolveSwitchComptimeLoop( special_members_only: ?SpecialProng, special_generic: SpecialProng, special_generic_is_under: bool, - case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), + case_vals: std.ArrayList(Air.Inst.Ref), scalar_cases_len: u32, multi_cases_len: u32, err_set: bool, @@ -12987,7 +12987,7 @@ fn resolveSwitchComptime( special_members_only: ?SpecialProng, special_generic: SpecialProng, special_generic_is_under: bool, - case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), + case_vals: std.ArrayList(Air.Inst.Ref), scalar_cases_len: u32, multi_cases_len: u32, err_set: bool, @@ -13243,7 +13243,7 @@ fn validateErrSetSwitch( sema: *Sema, block: *Block, seen_errors: *SwitchErrorSet, - case_vals: *std.ArrayListUnmanaged(Air.Inst.Ref), + case_vals: *std.ArrayList(Air.Inst.Ref), operand_ty: Type, inst_data: @FieldType(Zir.Inst.Data, "pl_node"), scalar_cases_len: u32, @@ -35563,8 +35563,8 @@ fn unionFields( enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len); } - var field_types: std.ArrayListUnmanaged(InternPool.Index) = .empty; - var field_aligns: std.ArrayListUnmanaged(InternPool.Alignment) = .empty; + var field_types: std.ArrayList(InternPool.Index) = .empty; + var field_aligns: std.ArrayList(InternPool.Alignment) = .empty; try field_types.ensureTotalCapacityPrecise(sema.arena, fields_len); if (small.any_aligned_fields) @@ -36923,7 +36923,7 @@ fn notePathToComptimeAllocPtr( const zcu = pt.zcu; const ip = &zcu.intern_pool; - var first_path: std.ArrayListUnmanaged(u8) = .empty; + var first_path: std.ArrayList(u8) = .empty; if (intermediate_value_count == 0) { try first_path.print(arena, "{f}", .{start_value_name.fmt(ip)}); } else { @@ -36994,7 +36994,7 @@ fn notePathToComptimeAllocPtr( } } -fn notePathToComptimeAllocPtrInner(sema: *Sema, val: Value, path: *std.ArrayListUnmanaged(u8)) Allocator.Error!Value { +fn notePathToComptimeAllocPtrInner(sema: *Sema, val: Value, path: *std.ArrayList(u8)) Allocator.Error!Value { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; From 10912f14c6b9a530fc455ad0d407a69ba3c6fe0c Mon Sep 17 00:00:00 2001 From: Jackson Wambolt Date: Sun, 7 Sep 2025 16:18:01 -0500 Subject: [PATCH 3/3] Sema: handle err union switch for !noreturn --- src/Sema.zig | 137 +++++++++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 59 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index d85cc5bb1a9d..876575d090ea 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11003,19 +11003,38 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); defer case_vals.deinit(gpa); + const operand_ty = sema.typeOf(raw_operand_val); + const operand_err_union_ty = if (extra.data.bits.payload_is_ref) + operand_ty.childType(zcu) + else + operand_ty; + + if (operand_err_union_ty.zigTypeTag(zcu) != .error_union) { + return sema.fail(block, switch_src, "expected error union type, found '{f}'", .{ + operand_ty.fmt(pt), + }); + } + + const operand_err_set_ty = operand_err_union_ty.errorUnionSet(zcu); + const operand_payload_ty = operand_err_union_ty.errorUnionPayload(zcu); + const NonError = struct { body: []const Zir.Inst.Index, end: usize, capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, }; - const non_error_case: NonError = non_error: { + const non_error_case: ?NonError, const non_err_case_end: usize = non_error: { const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]); const extra_body_start = header_extra_index + 1; + const non_err_case_end = extra_body_start + info.body_len; break :non_error .{ - .body = sema.code.bodySlice(extra_body_start, info.body_len), - .end = extra_body_start + info.body_len, - .capture = info.capture, + if (operand_payload_ty.isNoReturn(zcu)) null else .{ + .body = sema.code.bodySlice(extra_body_start, info.body_len), + .end = non_err_case_end, + .capture = info.capture, + }, + non_err_case_end, }; }; @@ -11028,12 +11047,12 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp const else_case: Else = if (!extra.data.bits.has_else) .{ .body = &.{}, - .end = non_error_case.end, + .end = non_err_case_end, .is_inline = false, .has_capture = false, } else special: { - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[non_error_case.end]); - const extra_body_start = non_error_case.end + 1; + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[non_err_case_end]); + const extra_body_start = non_err_case_end + 1; assert(info.capture != .by_ref); assert(!info.has_tag_capture); break :special .{ @@ -11047,20 +11066,6 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp var seen_errors = SwitchErrorSet.init(gpa); defer seen_errors.deinit(); - const operand_ty = sema.typeOf(raw_operand_val); - const operand_err_set = if (extra.data.bits.payload_is_ref) - operand_ty.childType(zcu) - else - operand_ty; - - if (operand_err_set.zigTypeTag(zcu) != .error_union) { - return sema.fail(block, switch_src, "expected error union type, found '{f}'", .{ - operand_ty.fmt(pt), - }); - } - - const operand_err_set_ty = operand_err_set.errorUnionSet(zcu); - const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); try sema.air_instructions.append(gpa, .{ .tag = .block, @@ -11100,7 +11105,12 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp const resolved_err_set = try sema.resolveInferredErrorSetTy(block, main_src, operand_err_set_ty.toIntern()); if (Type.fromInterned(resolved_err_set).errorSetIsEmpty(zcu)) { - return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges); + return if (non_error_case) |nec| + sema.resolveBlockBody(block, main_operand_src, &child_block, nec.body, inst, merges) + else unreach: { + try sema.analyzeUnreachable(block, main_operand_src, false); + break :unreach .unreachable_value; + }; } const else_error_ty: ?Type = try validateErrSetSwitch( @@ -11138,7 +11148,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp ov; if (operand_val.errorUnionIsPayload(zcu)) { - return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges); + return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.?.body, inst, merges); } else { const err_val = Value.fromInterned(try pt.intern(.{ .err = .{ @@ -11184,7 +11194,12 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp if (scalar_cases_len + multi_cases_len == 0) { if (else_error_ty) |ty| if (ty.errorSetIsEmpty(zcu)) { - return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges); + return if (non_error_case) |nec| + sema.resolveBlockBody(block, main_operand_src, &child_block, nec.body, inst, merges) + else unreach: { + try sema.analyzeUnreachable(block, main_operand_src, false); + break :unreach .unreachable_value; + }; }; } @@ -11193,15 +11208,6 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp unreachable; } - const cond = if (extra.data.bits.payload_is_ref) blk: { - try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val).elemType2(zcu)); - const loaded = try sema.analyzeLoad(block, main_src, raw_operand_val, main_src); - break :blk try sema.analyzeIsNonErr(block, main_src, loaded); - } else blk: { - try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val)); - break :blk try sema.analyzeIsNonErr(block, main_src, raw_operand_val); - }; - var sub_block = child_block.makeSubBlock(); sub_block.runtime_loop = null; sub_block.runtime_cond = main_operand_src; @@ -11209,10 +11215,6 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp sub_block.need_debug_scope = null; // this body is emitted regardless defer sub_block.instructions.deinit(gpa); - const non_error_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, non_error_case.body); - const true_instructions = try sub_block.instructions.toOwnedSlice(gpa); - defer gpa.free(true_instructions); - spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref) try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val) else @@ -11257,31 +11259,48 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp &.{}, &.{}, ); + const err_switch_instructions = try sub_block.instructions.toOwnedSlice(gpa); + defer gpa.free(err_switch_instructions); + + if (non_error_case) |nec| { + const cond = if (extra.data.bits.payload_is_ref) blk: { + try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val).elemType2(zcu)); + const loaded = try sema.analyzeLoad(block, main_src, raw_operand_val, main_src); + break :blk try sema.analyzeIsNonErr(block, main_src, loaded); + } else blk: { + try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val)); + break :blk try sema.analyzeIsNonErr(block, main_src, raw_operand_val); + }; - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len + - true_instructions.len + sub_block.instructions.items.len); + const non_error_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, nec.body); - _ = try child_block.addInst(.{ - .tag = .cond_br, - .data = .{ - .pl_op = .{ - .operand = cond, - .payload = sema.addExtraAssumeCapacity(Air.CondBr{ - .then_body_len = @intCast(true_instructions.len), - .else_body_len = @intCast(sub_block.instructions.items.len), - .branch_hints = .{ - .true = non_error_hint, - .false = .none, - // Code coverage is desired for error handling. - .then_cov = .poi, - .else_cov = .poi, - }, - }), + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len + + err_switch_instructions.len + sub_block.instructions.items.len); + + _ = try child_block.addInst(.{ + .tag = .cond_br, + .data = .{ + .pl_op = .{ + .operand = cond, + .payload = sema.addExtraAssumeCapacity(Air.CondBr{ + .then_body_len = @intCast(sub_block.instructions.items.len), + .else_body_len = @intCast(err_switch_instructions.len), + .branch_hints = .{ + .true = non_error_hint, + .false = .none, + // Code coverage is desired for error handling. + .then_cov = .poi, + .else_cov = .poi, + }, + }), + }, }, - }, - }); - sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions)); - sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items)); + }); + sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items)); + sema.air_extra.appendSliceAssumeCapacity(@ptrCast(err_switch_instructions)); + } else { + try child_block.instructions.appendSlice(gpa, err_switch_instructions); + } return sema.resolveAnalyzedBlock(block, main_src, &child_block, merges, false); }