From ed1268d0e63d3af144a262a1fa41b8e69ab3073b Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sun, 11 Jan 2026 13:16:25 +0000 Subject: [PATCH] Zir: simplify '_' prong of 'switch' statements --- lib/std/zig/AstGen.zig | 88 ++++----------- lib/std/zig/Zir.zig | 123 ++++----------------- src/Sema.zig | 241 +++++++++++++++++++---------------------- src/Zcu.zig | 42 +------ src/print_zir.zig | 84 +++++++------- 5 files changed, 188 insertions(+), 390 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 01a8373e3f..667f5ef045 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -7267,11 +7267,7 @@ fn switchExpr( var total_items_len: usize = 0; var total_ranges_len: usize = 0; var else_case_node: Ast.Node.OptionalIndex = .none; - var else_src: ?Ast.TokenIndex = null; - var under_case_node: Ast.Node.OptionalIndex = .none; var underscore_node: Ast.Node.OptionalIndex = .none; - var underscore_src: ?Ast.TokenIndex = null; - var under_is_bare = false; for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; if (case.payload_token) |payload_token| { @@ -7304,22 +7300,21 @@ fn switchExpr( // Check for else prong. if (case.ast.values.len == 0) { - const case_src = case.ast.arrow_token - 1; - if (else_src) |src| { + if (else_case_node.unwrap()) |prev_case_node| { + const prev_else_tok = tree.fullSwitchCase(prev_case_node).?.ast.arrow_token - 1; + const else_tok = case.ast.arrow_token - 1; return astgen.failTokNotes( - case_src, + else_tok, "multiple else prongs in switch expression", .{}, - &.{try astgen.errNoteTok(src, "previous else prong here", .{})}, + &.{try astgen.errNoteTok(prev_else_tok, "previous else prong here", .{})}, ); } else_case_node = case_node.toOptional(); - else_src = case_src; continue; } // Check for '_' prong and ranges. - var case_has_underscore = false; var case_has_ranges = false; for (case.ast.values) |val| { switch (tree.nodeTag(val)) { @@ -7329,10 +7324,10 @@ fn switchExpr( }, .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}), else => |tag| { + total_items_len += 1; if (tag == .identifier and mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { - const val_src = tree.nodeMainToken(val); if (is_err_switch) { const case_src = case.ast.arrow_token - 1; return astgen.failTokNotes( @@ -7348,30 +7343,24 @@ fn switchExpr( }, ); } - if (underscore_src) |src| { - return astgen.failTokNotes( - val_src, + if (underscore_node.unwrap()) |prev_src| { + return astgen.failNodeNotes( + val, "multiple '_' prongs in switch expression", .{}, - &.{try astgen.errNoteTok(src, "previous '_' prong here", .{})}, + &.{try astgen.errNoteNode(prev_src, "previous '_' prong here", .{})}, ); } if (case.inline_token != null) { - return astgen.failTok(val_src, "cannot inline '_' prong", .{}); + return astgen.failNode(val, "cannot inline '_' prong", .{}); } - under_case_node = case_node.toOptional(); - underscore_src = val_src; underscore_node = val.toOptional(); - under_is_bare = case.ast.values.len == 1; - case_has_underscore = true; - } else { - total_items_len += 1; } }, } } - const case_len = case.ast.values.len - @intFromBool(case_has_underscore); + const case_len = case.ast.values.len; if (case_len == 1 and !case_has_ranges) { scalar_cases_len += 1; } else if (case_len >= 1) { @@ -7379,9 +7368,8 @@ fn switchExpr( } } - const has_else = else_src != null; - const has_under = underscore_src != null; - if (under_is_bare) assert(has_under); // make sure that the former implies the latter + const has_else = else_case_node != .none; + const has_under = underscore_node != .none; if (is_err_switch) assert(!has_under); // should have failed by now const any_ranges = total_ranges_len > 0; @@ -7430,10 +7418,8 @@ fn switchExpr( var non_err_prong_body_start: u32 = undefined; var else_prong_body_start: u32 = undefined; - var bare_under_prong_body_start: u32 = undefined; var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined; var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined; - var under_extra: u32 = undefined; var block_scope = parent_gz.makeSubBlock(scope); // block_scope not used for collecting instructions @@ -7688,7 +7674,6 @@ fn switchExpr( for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; - const case_has_under = case_node.toOptional() == under_case_node; const ranges_len: u32 = if (any_ranges) blk: { var ranges_len: u32 = 0; for (case.ast.values) |value| { @@ -7696,14 +7681,13 @@ fn switchExpr( } break :blk ranges_len; } else 0; - const items_len: u32 = @intCast(case.ast.values.len - ranges_len - @intFromBool(case_has_under)); + const items_len: u32 = @intCast(case.ast.values.len - ranges_len); const is_multi_case = items_len > 1 or ranges_len > 0; // item/range bodies in order of occurence var item_i: usize = 0; var range_i: usize = 0; for (case.ast.values) |value| { - if (value.toOptional() == underscore_node) continue; const is_range = tree.nodeTag(value) == .switch_range; const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined; const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value}; @@ -7722,16 +7706,9 @@ fn switchExpr( const str_index = try astgen.identAsString(ident_token); break :blk .wrap(.{ .error_value = str_index }); }, - .number_literal => { - // We don't actually need a final result type for number - // literals, they can just be turned into `comptime_int` - // or `comptime_float` as usual and then be coerced to - // the correct type later during semantic analysis. - assert(scratch_scope.instructions_top == GenZir.unstacked_top); // important! we emit into `parent_gz` which `scratch_scope` is stacked on top of - const zir_ref = try comptimeExpr(parent_gz, scope, .{ .rl = .none }, item, .switch_item); - break :blk .wrap(.{ .number_literal = zir_ref }); - }, - else => { + else => if (value.toOptional() == underscore_node) { + break :blk .wrap(.under); + } else { scratch_scope.instructions_top = parent_gz.instructions.items.len; defer scratch_scope.unstack(); const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal); @@ -7957,25 +7934,6 @@ fn switchExpr( break :prong_body; } - if (case_has_under) { - // We're either writing under_prong_info or under_index here. - if (under_is_bare) { - assert(case.ast.values.len == 1); // only `_` - const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{ - .body_len = @intCast(body_len), - .capture = capture, - .has_tag_capture = has_tag_capture, - }; - under_extra = @bitCast(bare_under_info); - bare_under_prong_body_start = body_start; - break :prong_body; - } else if (is_multi_case) { - under_extra = scalar_cases_len + multi_case_index; - } else { - under_extra = scalar_case_index; - } - } - // We allow prongs with error items which are not inside the error set // being switched on if their body is `=> comptime unreachable,`. const is_comptime_unreach = comptime_unreach: { @@ -8003,7 +7961,7 @@ fn switchExpr( } } } - assert(scalar_case_index + multi_case_index + @intFromBool(has_else) + @intFromBool(under_is_bare) == case_nodes.len); + assert(scalar_case_index + multi_case_index + @intFromBool(has_else) == case_nodes.len); assert(multi_items_infos_start + multi_item_offset == bodies_start); if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) { @@ -8024,7 +7982,6 @@ fn switchExpr( @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset @intFromBool(needs_non_err_handling) + // non_err_info @intFromBool(has_else) + // else_info - @intFromBool(has_under) + // under_prong_info or under_index payloads.items.len - body_table_end); // item infos and bodies // singular pieces of data @@ -8035,7 +7992,6 @@ fn switchExpr( .any_ranges = any_ranges, .has_else = has_else, .has_under = has_under, - .under_is_bare = under_is_bare, .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, .any_maybe_runtime_capture = any_maybe_runtime_capture, .payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder, @@ -8054,7 +8010,6 @@ fn switchExpr( astgen.extra.appendAssumeCapacity(@bitCast(non_err_info)); } if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info)); - if (has_under) astgen.extra.appendAssumeCapacity(under_extra); const extra_payloads_start = astgen.extra.items.len; @@ -8070,11 +8025,6 @@ fn switchExpr( const body = payloads.items[else_prong_body_start..][0..else_info.body_len]; astgen.extra.appendSliceAssumeCapacity(body); } - if (under_is_bare) { - const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra); - const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len]; - astgen.extra.appendSliceAssumeCapacity(body); - } for (0..scalar_cases_len) |scalar_i| { const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]); const item_body_start = payloads.items[scalar_body_table + scalar_i]; diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 092886ffd1..386217d378 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -3303,34 +3303,25 @@ pub const Inst = struct { /// 3. catch_or_if_src_node_offset: Ast.Node.Offset, // If inst is switch_block_err_union. /// 4. non_err_info: ProngInfo.NonErr, // If inst is switch_block_err_union. /// 5. else_info: ProngInfo.Else, // If has_else is set. - /// 6. under_info: ProngInfo.Under, // If has_under is set and - /// // under_is_bare is set. - /// 7. under_index: u32, // If has_under is set and - /// // under_is_bare is *not* set. - /// // Index into switch cases. - /// 8. scalar_prong_info: ProngInfo, // for every scalar_cases_len - /// 9. multi_prong_info: ProngInfo, // for every multi_cases_len - /// 10. multi_case_items_len: u32, // for every multi_cases_len - /// 11. multi_case_ranges_len: u32, // If has_ranges is set: for every multi_cases_len - /// 12. scalar_item_info: ItemInfo, // for every scalar_cases_len - /// 13. multi_items_info: { // for every multi_cases_len + /// 6. scalar_prong_info: ProngInfo, // for every scalar_cases_len + /// 7. multi_prong_info: ProngInfo, // for every multi_cases_len + /// 8. multi_case_items_len: u32, // for every multi_cases_len + /// 9. multi_case_ranges_len: u32, // If has_ranges is set: for every multi_cases_len + /// 10. scalar_item_info: ItemInfo, // for every scalar_cases_len + /// 11. multi_items_info: { // for every multi_cases_len /// item_info: ItemInfo, // for each multi_case_items_len /// range_items_info: { // for each multi_case_ranges_len /// first_info: ItemInfo, /// last_info: ItemInfo, /// } /// } - /// 14. non_err_body { + /// 12. non_err_body { /// body_inst: Index // for every non_err_info.body_len /// } - /// 15. else_body: { // If has_else is set. + /// 13. else_body: { // If has_else is set. /// body_inst: Inst.Index, // for every else_info.body_len /// } - /// 16. under_body: { // If has_under is set and - /// // under_is_bare is set. - /// body_inst: Inst.Index, // for every under_info.body_len - /// } - /// 17. scalar_bodies: { // for every scalar_cases_len + /// 14. scalar_bodies: { // for every scalar_cases_len /// prong_body: { // for each body_len in scalar_prong_info /// body_inst: Inst.Index, // for every body_len /// } @@ -3338,7 +3329,7 @@ pub const Inst = struct { /// body_inst: Inst.Index, // for every body_len /// } /// } - /// 18. multi_bodies: { // for each multi_items_info + /// 15. multi_bodies: { // for each multi_items_info /// prong_body: { /// body_inst: Inst.Index, // for each multi_prong_info.body_len /// } @@ -3363,8 +3354,6 @@ pub const Inst = struct { any_ranges: bool, has_else: bool, has_under: bool, - /// Only valid if `has_under` is also set. - under_is_bare: bool, /// If true, at least one prong contains a `continue`. /// Only valid if `has_label` is set. has_continue: bool, @@ -3377,7 +3366,7 @@ pub const Inst = struct { // NOTE maybe don't steal any more bits from poor `scalar_cases_len` // and split `Bits` into two parts instead, `raw_operand` surely // wouldn't mind donating a couple of bits for that purpose... - pub const ScalarCasesLen = u23; + pub const ScalarCasesLen = u24; }; pub const ProngInfo = packed struct(u32) { @@ -3406,12 +3395,6 @@ pub const Inst = struct { has_tag_capture: bool, is_simple_noreturn: bool, }; - - pub const BareUnder = packed struct(u32) { - body_len: u29, - capture: ProngInfo.Capture, - has_tag_capture: bool, - }; }; pub const ItemInfo = packed struct(u32) { @@ -3421,23 +3404,23 @@ pub const Inst = struct { pub const Kind = enum(u2) { enum_literal, error_value, - number_literal, body_len, + under, }; pub const Unwrapped = union(ItemInfo.Kind) { enum_literal: Zir.NullTerminatedString, error_value: Zir.NullTerminatedString, - number_literal: Inst.Ref, body_len: u32, + under, }; pub fn wrap(unwrapped: ItemInfo.Unwrapped) ItemInfo { const data_uncasted: u32 = switch (unwrapped) { .enum_literal => |str_index| @intFromEnum(str_index), .error_value => |str_index| @intFromEnum(str_index), - .number_literal => |zir_ref| @intFromEnum(zir_ref), .body_len => |body_len| body_len, + .under => 0, }; return .{ .kind = unwrapped, .data = @intCast(data_uncasted) }; } @@ -3446,8 +3429,8 @@ pub const Inst = struct { return switch (item_info.kind) { .enum_literal => .{ .enum_literal = @enumFromInt(item_info.data) }, .error_value => .{ .error_value = @enumFromInt(item_info.data) }, - .number_literal => .{ .number_literal = @enumFromInt(item_info.data) }, .body_len => .{ .body_len = item_info.data }, + .under => .under, }; } @@ -4818,9 +4801,6 @@ fn findTrackableInner( if (zir_switch.else_case) |else_case| { try zir.findTrackableBody(gpa, contents, defers, else_case.body); } - if (zir_switch.under_case.resolve()) |under_case| { - try zir.findTrackableBody(gpa, contents, defers, under_case.body); - } var extra_index = zir_switch.end; var case_it = zir_switch.iterateCases(); while (case_it.next()) |case| { @@ -5268,16 +5248,6 @@ pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchB extra_index += 1; break :else_info else_info; } else undefined; - const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = if (bits.has_under and bits.under_is_bare) bare_under_info: { - const bare_under_info: Inst.SwitchBlock.ProngInfo.BareUnder = @bitCast(zir.extra[extra_index]); - extra_index += 1; - break :bare_under_info bare_under_info; - } else undefined; - const under_index: u32 = if (bits.has_under and !bits.under_is_bare) under_index: { - const under_index = zir.extra[extra_index]; - extra_index += 1; - break :under_index under_index; - } else undefined; const scalar_cases_len: u32 = bits.scalar_cases_len; const prong_infos: []const Inst.SwitchBlock.ProngInfo = @ptrCast(zir.extra[extra_index..][0 .. scalar_cases_len + multi_cases_len]); @@ -5320,20 +5290,6 @@ pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchB .is_simple_noreturn = else_info.is_simple_noreturn, }; } else null; - const under_case: UnwrappedSwitchBlock.Case.Under = if (bits.has_under) under_case: { - if (bits.under_is_bare) { - const body = zir.bodySlice(extra_index, bare_under_info.body_len); - extra_index += body.len; - break :under_case .{ .bare = .{ - .index = .bare_under, - .body = body, - .capture = bare_under_info.capture, - .has_tag_capture = bare_under_info.has_tag_capture, - } }; - } else { - break :under_case .{ .index = under_index }; - } - } else .none; return .{ .main_operand = extra.data.raw_operand, .switch_src_node_offset = inst_data.src_node, @@ -5344,7 +5300,7 @@ pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchB .any_maybe_runtime_capture = bits.any_maybe_runtime_capture, .non_err_case = non_err_case, .else_case = else_case, - .under_case = under_case, + .has_under = bits.has_under, .prong_infos = prong_infos, .multi_case_items_lens = multi_case_items_lens, .multi_case_ranges_lens = multi_case_ranges_lens, @@ -5377,7 +5333,7 @@ pub const UnwrappedSwitchBlock = struct { any_maybe_runtime_capture: bool, non_err_case: ?Case.NonErr, else_case: ?Case.Else, - under_case: Case.Under, + has_under: bool, // Refer to doc comment and `iterateCases` to access everything below correctly. prong_infos: []const Inst.SwitchBlock.ProngInfo, multi_case_items_lens: []const u32, @@ -5411,25 +5367,13 @@ pub const UnwrappedSwitchBlock = struct { item_infos: []const Inst.SwitchBlock.ItemInfo, range_infos: []const [2]Inst.SwitchBlock.ItemInfo, - pub fn isUnder(case: *const Case) bool { - return case.index.is_under; - } - pub const Index = packed struct(u32) { kind: enum(u1) { scalar, multi }, - is_under: bool, - value: u30, + value: u31, pub const @"else": Case.Index = .{ .kind = .scalar, - .is_under = false, - .value = std.math.maxInt(u30), - }; - - pub const bare_under: Case.Index = .{ - .kind = .scalar, - .is_under = true, - .value = std.math.maxInt(u30), + .value = std.math.maxInt(u31), }; }; @@ -5448,31 +5392,8 @@ pub const UnwrappedSwitchBlock = struct { is_simple_noreturn: bool, }; - pub const Under = union(enum) { - none, - bare: Under.Resolved, - index: u32, - - pub const Resolved = struct { - index: Case.Index, - body: []const Inst.Index, - capture: Inst.SwitchBlock.ProngInfo.Capture, - has_tag_capture: bool, - }; - - /// If this returns `null` and `under` is not `.none`, you'll have to - /// find the under case by iterating all cases and using `isUnder`! - pub fn resolve(under: Under) ?Under.Resolved { - return switch (under) { - .bare => |resolved| resolved, - .none, .index => null, - }; - } - }; - pub const Iterator = struct { next_idx: u32, - under_idx: ?u32, prong_infos: []const Inst.SwitchBlock.ProngInfo, multi_case_items_lens: []const u32, multi_case_ranges_lens: ?[]const u32, @@ -5486,7 +5407,6 @@ pub const UnwrappedSwitchBlock = struct { return if (idx < scalar_cases_len) .{ .index = .{ .kind = .scalar, - .is_under = idx == it.under_idx, .value = @intCast(idx), }, .prong_info = it.prong_infos[idx], @@ -5495,7 +5415,6 @@ pub const UnwrappedSwitchBlock = struct { } else .{ .index = .{ .kind = .multi, - .is_under = idx == it.under_idx, .value = @intCast(idx - scalar_cases_len), }, .prong_info = it.prong_infos[idx], @@ -5516,10 +5435,6 @@ pub const UnwrappedSwitchBlock = struct { pub fn iterateCases(unwrapped: UnwrappedSwitchBlock) Case.Iterator { return .{ .next_idx = 0, - .under_idx = switch (unwrapped.under_case) { - .none, .bare => null, - .index => |index| index, - }, .prong_infos = unwrapped.prong_infos, .multi_case_items_lens = unwrapped.multi_case_items_lens, .multi_case_ranges_lens = unwrapped.multi_case_ranges_lens, diff --git a/src/Sema.zig b/src/Sema.zig index 204d4a736c..659f1d4987 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10753,10 +10753,9 @@ fn analyzeSwitchBlock( const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset }); const has_else = zir_switch.else_case != null; - const has_under = zir_switch.under_case != .none; + const has_under = zir_switch.has_under; const else_case = validated_switch.else_case; - const under_case = validated_switch.under_case; const operand: SwitchOperand, const operand_ty: Type, const maybe_operand_opv: ?Value, const item_ty: Type = operand: { const val, const ref = if (operand_is_ref) @@ -10933,9 +10932,6 @@ fn analyzeSwitchBlock( // we allow simple noreturn else prongs when switching on error sets! break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true }; } - if (has_under) { - break :find_prong .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture, false, true }; - } unreachable; // malformed validated switch }; @@ -11083,10 +11079,9 @@ fn finishSwitchBr( const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset }); const has_else = zir_switch.else_case != null; - const has_under = zir_switch.under_case != .none; + const has_under = zir_switch.has_under; const else_case = validated_switch.else_case; - const under_case = validated_switch.under_case; const scalar_cases_len = zir_switch.scalarCasesLen(); const multi_cases_len = zir_switch.multiCasesLen(); @@ -11138,20 +11133,15 @@ fn finishSwitchBr( var case_it = zir_switch.iterateCases(); var extra_index = zir_switch.end; + var under_prong: ?struct { + index: Zir.UnwrappedSwitchBlock.Case.Index, + body: []const Zir.Inst.Index, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + has_tag_capture: bool, + } = null; + var cases_len: u32 = 0; while (case_it.next()) |case| { - if (case.isUnder()) { // we'll deal with this later - extra_index += case.prong_info.body_len; - for (case.item_infos) |item_info| { - if (item_info.bodyLen()) |body_len| extra_index += body_len; - } - for (case.range_infos) |range_info| { - if (range_info[0].bodyLen()) |body_len| extra_index += body_len; - if (range_info[1].bodyLen()) |body_len| extra_index += body_len; - } - continue; - } - const item_refs = case_vals[case_val_idx..][0..case.item_infos.len]; case_val_idx += item_refs.len; const range_refs: []const [2]Air.Inst.Ref = @@ -11171,7 +11161,9 @@ fn finishSwitchBr( var emit_bb = false; var any_analyze_body = false; + var is_under_prong = false; for (case.item_infos, item_refs, 0..) |item_info, item_ref, item_i| { + if (item_ref == .none) is_under_prong = true; if (item_info.bodyLen()) |body_len| extra_index += body_len; const analyze_body = sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, union_originally, err_set, prong_info.is_comptime_unreach); @@ -11319,48 +11311,58 @@ fn finishSwitchBr( } } - if (!prong_info.is_inline) { - cases_len += 1; - case_block.instructions.clearRetainingCapacity(); - case_block.error_return_trace_index = child_block.error_return_trace_index; + if (prong_info.is_inline) continue; // handled above - const prong_hint: std.builtin.BranchHint = hint: { - if (any_analyze_body) break :hint try sema.analyzeSwitchProng( - &case_block, - operand, - operand_ty, - raw_operand_ty, - prong_body, - block.src(.{ .switch_capture = .{ - .switch_node_offset = src_node_offset, - .case_idx = case.index, - } }), - prong_info.capture, - prong_info.has_tag_capture, - .none, - .{ .item_refs = item_refs }, - validated_switch.else_err_ty, - switch_inst, - zir_switch, - ); - _ = try case_block.addNoOp(.unreach); - break :hint .cold; // unreachable branches are cold + if (is_under_prong) { + under_prong = .{ + .index = case.index, + .body = prong_body, + .capture = case.prong_info.capture, + .has_tag_capture = case.prong_info.has_tag_capture, }; - try branch_hints.append(gpa, prong_hint); - - try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + - item_refs.len + - 2 * range_refs.len + - case_block.instructions.items.len); - cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{ - .items_len = @intCast(item_refs.len), - .ranges_len = @intCast(range_refs.len), - .body_len = @intCast(case_block.instructions.items.len), - })); - cases_extra.appendSliceAssumeCapacity(@ptrCast(item_refs)); - cases_extra.appendSliceAssumeCapacity(@ptrCast(range_refs)); - cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); + continue; } + + cases_len += 1; + case_block.instructions.clearRetainingCapacity(); + case_block.error_return_trace_index = child_block.error_return_trace_index; + + const prong_hint: std.builtin.BranchHint = hint: { + if (any_analyze_body) break :hint try sema.analyzeSwitchProng( + &case_block, + operand, + operand_ty, + raw_operand_ty, + prong_body, + block.src(.{ .switch_capture = .{ + .switch_node_offset = src_node_offset, + .case_idx = case.index, + } }), + prong_info.capture, + prong_info.has_tag_capture, + .none, + .{ .item_refs = item_refs }, + validated_switch.else_err_ty, + switch_inst, + zir_switch, + ); + _ = try case_block.addNoOp(.unreach); + break :hint .cold; // unreachable branches are cold + }; + try branch_hints.append(gpa, prong_hint); + + try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + + item_refs.len + + 2 * range_refs.len + + case_block.instructions.items.len); + cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{ + .items_len = @intCast(item_refs.len), + .ranges_len = @intCast(range_refs.len), + .body_len = @intCast(case_block.instructions.items.len), + })); + cases_extra.appendSliceAssumeCapacity(@ptrCast(item_refs)); + cases_extra.appendSliceAssumeCapacity(@ptrCast(range_refs)); + cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); } const catch_all_extra: []const u32 = catch_all_extra: { @@ -11499,7 +11501,7 @@ fn finishSwitchBr( try branch_hints.append(gpa, prong_hint); try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + - (validated_switch.seen_enum_fields.len - zir_switch.totalItemsLen()) + + (validated_switch.seen_enum_fields.len + 1 - zir_switch.totalItemsLen()) + // +1 because totalItemsLen includes the _ case_block.instructions.items.len); const extra_case = cases_extra.addManyAsArrayAssumeCapacity( @typeInfo(Air.SwitchBr.Case).@"struct".fields.len, @@ -11555,7 +11557,7 @@ fn finishSwitchBr( if (analyze_catch_all_body) { const index, const body, const capture, const has_tag_capture = switch (catch_all_case) { .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture }, - .under => .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture }, + .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture }, .none => unreachable, }; break :hint try sema.analyzeSwitchProng( @@ -11734,7 +11736,6 @@ fn fixupSwitchContinues( const ValidatedSwitchBlock = struct { seen_enum_fields: []const ?LazySrcLoc, seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc), - seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc), seen_ranges: []const RangeSet.Range, true_src: ?LazySrcLoc, false_src: ?LazySrcLoc, @@ -11742,7 +11743,6 @@ const ValidatedSwitchBlock = struct { case_vals: []const Air.Inst.Ref, else_case: Zir.UnwrappedSwitchBlock.Case.Else, - under_case: Zir.UnwrappedSwitchBlock.Case.Under.Resolved, else_err_ty: ?Type, fn iterateUnhandledItems( @@ -11868,7 +11868,6 @@ fn validateSwitchBlock( const src = block.nodeOffset(src_node_offset); const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset }); const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset }); - const under_prong_src = block.src(.{ .node_offset_switch_under_prong = src_node_offset }); var extra_index = zir_switch.end; // We want to map values to our placeholders later on. @@ -11948,33 +11947,7 @@ fn validateSwitchBlock( }; const has_else = zir_switch.else_case != null; - const has_under = zir_switch.under_case != .none; - - // Validate usage of '_' prongs. - if (has_under and !operand_ty.isNonexhaustiveEnum(zcu)) { - const msg = msg: { - const msg = try sema.errMsg( - src, - "'_' prong only allowed when switching on non-exhaustive enums", - .{}, - ); - errdefer msg.destroy(gpa); - try sema.errNote( - under_prong_src, - msg, - "'_' prong here", - .{}, - ); - try sema.errNote( - src, - msg, - "consider using 'else'", - .{}, - ); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(block, msg); - } + const has_under = zir_switch.has_under; var case_vals: std.ArrayList(Air.Inst.Ref) = .empty; try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len); @@ -11991,7 +11964,6 @@ fn validateSwitchBlock( var else_err_ty: ?Type = null; const else_case = zir_switch.else_case orelse undefined; - var under_case = zir_switch.under_case.resolve() orelse undefined; switch (item_ty.zigTypeTag(zcu)) { .@"union" => unreachable, @@ -12020,16 +11992,6 @@ fn validateSwitchBlock( var case_it = zir_switch.iterateCases(); while (case_it.next()) |case| { const prong_info = case.prong_info; - const is_under = case.isUnder(); - if (is_under) { - assert(!prong_info.is_inline); - under_case = .{ - .index = case.index, - .body = sema.code.bodySlice(extra_index, prong_info.body_len), - .capture = prong_info.capture, - .has_tag_capture = prong_info.has_tag_capture, - }; - } extra_index += prong_info.body_len; for (case.item_infos, 0..) |item_info, item_i| { const item_src = block.src(.{ .switch_case_item = .{ @@ -12037,9 +11999,34 @@ fn validateSwitchBlock( .case_idx = case.index, .item_idx = .{ .kind = .single, .value = @intCast(item_i) }, } }); - const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline); - try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src); - if (!is_under) case_vals.appendAssumeCapacity(item.ref); + if (item_info.unwrap() == .under) { + if (!operand_ty.isNonexhaustiveEnum(zcu)) return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg( + src, + "'_' prong only allowed when switching on non-exhaustive enums", + .{}, + ); + errdefer msg.destroy(gpa); + try sema.errNote( + item_src, + msg, + "'_' prong here", + .{}, + ); + try sema.errNote( + src, + msg, + "consider using 'else'", + .{}, + ); + break :msg msg; + }); + case_vals.appendAssumeCapacity(.none); + } else { + const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach); + try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src); + case_vals.appendAssumeCapacity(item.ref); + } } for (case.range_infos, 0..) |range_info, range_i| { const range_offset: LazySrcLoc.Offset.SwitchItem = .{ @@ -12050,10 +12037,10 @@ fn validateSwitchBlock( const range_src = block.src(.{ .switch_case_item = range_offset }); const first_src = block.src(.{ .switch_case_item_range_first = range_offset }); const last_src = block.src(.{ .switch_case_item_range_last = range_offset }); - const first_item, extra_index = try sema.resolveSwitchItem(block, first_src, item_ty, range_info[0], extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline); - const last_item, extra_index = try sema.resolveSwitchItem(block, last_src, item_ty, range_info[1], extra_index, switch_inst, prong_info.is_comptime_unreach, prong_info.is_inline); + const first_item, extra_index = try sema.resolveSwitchItem(block, first_src, item_ty, range_info[0], extra_index, switch_inst, prong_info.is_comptime_unreach); + const last_item, extra_index = try sema.resolveSwitchItem(block, last_src, item_ty, range_info[1], extra_index, switch_inst, prong_info.is_comptime_unreach); try sema.validateSwitchItemOrRange(block, range_src, first_item.val, last_item.val, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src); - if (!is_under) case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref }); + case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref }); } } @@ -12285,7 +12272,6 @@ fn validateSwitchBlock( return .{ .seen_enum_fields = seen_enum_fields, .seen_errors = seen_errors, - .seen_sparse_values = seen_sparse_values, .seen_ranges = range_set.ranges.items, .true_src = true_src, .false_src = false_src, @@ -12293,7 +12279,6 @@ fn validateSwitchBlock( .case_vals = case_vals.items, .else_case = else_case, - .under_case = under_case, .else_err_ty = else_err_ty, }; } @@ -12334,19 +12319,13 @@ fn resolveSwitchBlock( var case_val_idx: usize = 0; var extra_index = zir_switch.end; var case_it = zir_switch.iterateCases(); + var under_prong: ?struct { + index: Zir.UnwrappedSwitchBlock.Case.Index, + body: []const Zir.Inst.Index, + capture: Zir.Inst.SwitchBlock.ProngInfo.Capture, + has_tag_capture: bool, + } = null; while (case_it.next()) |case| { - if (case.isUnder()) { // we'll deal with this later - extra_index += case.prong_info.body_len; - for (case.item_infos) |item_info| { - if (item_info.bodyLen()) |body_len| extra_index += body_len; - } - for (case.range_infos) |range_info| { - if (range_info[0].bodyLen()) |body_len| extra_index += body_len; - if (range_info[1].bodyLen()) |body_len| extra_index += body_len; - } - continue; - } - const prong_info = case.prong_info; const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len); extra_index += prong_body.len; @@ -12363,6 +12342,15 @@ fn resolveSwitchBlock( const range_refs: []const [2]Air.Inst.Ref = @ptrCast(case_vals[case_val_idx..][0 .. 2 * case.range_infos.len]); case_val_idx += 2 * range_refs.len; for (item_refs) |item_ref| { + if (item_ref == .none) { + under_prong = .{ + .index = case.index, + .body = prong_body, + .capture = case.prong_info.capture, + .has_tag_capture = case.prong_info.has_tag_capture, + }; + continue; + } const item_val = sema.resolveConstDefinedValue(child_block, .unneeded, item_ref, undefined) catch unreachable; if (cond_val.eql(item_val, item_ty, zcu)) { if (err_set) try sema.maybeErrorUnwrapComptime(child_block, prong_body, cond_ref); @@ -12421,7 +12409,6 @@ fn resolveSwitchBlock( } const else_case = validated_switch.else_case; - const under_case = validated_switch.under_case; // named-only prong @@ -12452,7 +12439,7 @@ fn resolveSwitchBlock( const index, const body, const capture, const has_tag_capture, const is_inline = switch (catch_all_case) { .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline }, - .under => .{ under_case.index, under_case.body, under_case.capture, under_case.has_tag_capture, false }, + .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture, false }, .none => unreachable, }; if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref); @@ -13169,7 +13156,6 @@ fn resolveSwitchItem( extra_index: usize, switch_inst: Zir.Inst.Index, prong_is_comptime_unreach: bool, - prong_is_inline: bool, ) CompileError!ResolvedSwitchItemAndExtraIndex { const pt = sema.pt; const zcu = pt.zcu; @@ -13180,6 +13166,7 @@ fn resolveSwitchItem( var end = extra_index; const uncoerced: Air.Inst.Ref, const uncoerced_ty: Type = uncoerced: switch (item_info.unwrap()) { + .under => unreachable, // caller must check this before calling us .enum_literal => |str_index| { const zir_str = sema.code.nullTerminatedString(str_index); const name = try ip.getOrPutString(gpa, io, pt.tid, zir_str, .no_embedded_nulls); @@ -13198,10 +13185,6 @@ fn resolveSwitchItem( } })); break :uncoerced .{ uncoerced, err_set_ty }; }, - .number_literal => |zir_ref| { - const uncoerced = try sema.resolveInst(zir_ref); - break :uncoerced .{ uncoerced, sema.typeOf(uncoerced) }; - }, .body_len => |body_len| { const body = sema.code.bodySlice(extra_index, body_len); end += body.len; @@ -13231,7 +13214,7 @@ fn resolveSwitchItem( .ok => if (try sema.resolveValue(uncoerced)) |uncoerced_val| { break :item_ref try sema.coerceInMemory(uncoerced_val, item_ty); }, - .missing_error => if (prong_is_comptime_unreach and !prong_is_inline) { + .missing_error => if (prong_is_comptime_unreach) { break :item_ref uncoerced; }, .from_anyerror => {}, diff --git a/src/Zcu.zig b/src/Zcu.zig index 6de010c993..1de99157ab 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1742,27 +1742,6 @@ pub const SrcLoc = struct { } else unreachable; }, - .node_offset_switch_under_prong => |node_off| { - const tree = try src_loc.file_scope.getTree(zcu); - const switch_node = node_off.toAbsolute(src_loc.base_node); - _, const extra_index = tree.nodeData(switch_node).node_and_extra; - const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index); - for (case_nodes) |case_node| { - const case = tree.fullSwitchCase(case_node).?; - for (case.ast.values) |val| { - if (tree.nodeTag(val) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) - { - return tree.tokensToSpan( - tree.firstToken(case_node), - tree.lastToken(case_node), - tree.nodeMainToken(val), - ); - } - } - } else unreachable; - }, - .node_offset_switch_range => |node_off| { const tree = try src_loc.file_scope.getTree(zcu); const switch_node = node_off.toAbsolute(src_loc.base_node); @@ -2176,7 +2155,6 @@ pub const SrcLoc = struct { var multi_i: u32 = 0; var scalar_i: u32 = 0; - var underscore_node: Ast.Node.OptionalIndex = .none; const case: Ast.full.SwitchCase = case: for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; if (case.ast.values.len == 0) { @@ -2185,17 +2163,6 @@ pub const SrcLoc = struct { } continue :case; } - if (underscore_node == .none) { - for (case.ast.values) |value| { - if (tree.nodeTag(value) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(value)), "_")) - { - underscore_node = value.toOptional(); - if (want_case_idx.is_under) break :case case; - if (case.ast.values.len == 1) continue :case; - } - } - } const is_multi = case.ast.values.len != 1 or tree.nodeTag(case.ast.values[0]) == .switch_range; @@ -2220,7 +2187,6 @@ pub const SrcLoc = struct { .switch_case_item_range_last, => |x| item_idx: { assert(want_case_idx != Zir.UnwrappedSwitchBlock.Case.Index.@"else"); - assert(want_case_idx != Zir.UnwrappedSwitchBlock.Case.Index.bare_under); break :item_idx x.item_idx; }, .switch_capture, .switch_tag_capture => { @@ -2247,9 +2213,7 @@ pub const SrcLoc = struct { .single => { var item_i: u32 = 0; for (case.ast.values) |item_node| { - if (item_node.toOptional() == underscore_node or - tree.nodeTag(item_node) == .switch_range) - { + if (tree.nodeTag(item_node) == .switch_range) { continue; } if (item_i != want_item_idx.value) { @@ -2456,10 +2420,6 @@ pub const LazySrcLoc = struct { /// by taking this AST node index offset from the containing base node, /// which points to a switch expression AST node. Next, navigate to the else prong. node_offset_switch_else_prong: Ast.Node.Offset, - /// The source location points to the `_` prong of a switch expression, found - /// by taking this AST node index offset from the containing base node, - /// which points to a switch expression AST node. Next, navigate to the `_` prong. - node_offset_switch_under_prong: Ast.Node.Offset, /// The source location points to all the ranges of a switch expression, found /// by taking this AST node index offset from the containing base node, /// which points to a switch expression AST node. Next, navigate to any of the diff --git a/src/print_zir.zig b/src/print_zir.zig index 0006207d6d..c3a494de84 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -2021,15 +2021,6 @@ const Writer = struct { try stream.writeAll("else => "); try self.writeBracedBody(stream, else_case.body); } - if (zir_switch.under_case.resolve()) |under_case| { - try stream.writeAll(",\n"); - try stream.splatByteAll(' ', self.indent); - - try self.writeSwitchCaptures(stream, under_case.capture, under_case.has_tag_capture, inst, &zir_switch); - - try stream.writeAll("_ => "); - try self.writeBracedBody(stream, under_case.body); - } var case_it = zir_switch.iterateCases(); while (case_it.next()) |case| { @@ -2043,14 +2034,8 @@ const Writer = struct { const prong_body = self.code.bodySlice(extra_index, prong_info.body_len); extra_index += prong_body.len; - var first_item: bool = true; - if (case.isUnder()) { - try stream.writeAll("_"); - first_item = false; - } - for (case.item_infos) |item_info| { - if (!first_item) try stream.writeAll(", "); - first_item = false; + for (case.item_infos, 0..) |item_info, i| { + if (i > 0) try stream.writeAll(", "); switch (item_info.unwrap()) { .enum_literal => |str_index| { @@ -2061,9 +2046,7 @@ const Writer = struct { const str = self.code.nullTerminatedString(str_index); try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); }, - .number_literal => |zir_ref| { - try self.writeInstRef(stream, zir_ref); - }, + .under => try stream.writeByte('_'), .body_len => |body_len| { const item_body = self.code.bodySlice(extra_index, body_len); extra_index += item_body.len; @@ -2071,33 +2054,40 @@ const Writer = struct { }, } } - for (case.range_infos) |range_info| { - if (!first_item) try stream.writeAll(", "); - first_item = false; - - var first_range_item = true; - for (&range_info) |item_info| { - if (!first_range_item) try stream.writeAll("..."); - first_range_item = false; - - switch (item_info.unwrap()) { - .enum_literal => |str_index| { - const str = self.code.nullTerminatedString(str_index); - try stream.print("\".{f}\"", .{std.zig.fmtString(str)}); - }, - .error_value => |str_index| { - const str = self.code.nullTerminatedString(str_index); - try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); - }, - .number_literal => |zir_ref| { - try self.writeInstRef(stream, zir_ref); - }, - .body_len => |body_len| { - const item_body = self.code.bodySlice(extra_index, body_len); - extra_index += item_body.len; - try self.writeBracedDecl(stream, item_body); - }, - } + for (case.range_infos, 0..) |range_info, i| { + if (i > 0 and case.item_infos.len == 0) try stream.writeAll(", "); + switch (range_info[0].unwrap()) { + .enum_literal => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\".{f}\"", .{std.zig.fmtString(str)}); + }, + .error_value => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); + }, + .under => unreachable, // '_..._' is not allowed + .body_len => |body_len| { + const item_body = self.code.bodySlice(extra_index, body_len); + extra_index += item_body.len; + try self.writeBracedDecl(stream, item_body); + }, + } + try stream.writeAll("..."); + switch (range_info[1].unwrap()) { + .enum_literal => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\".{f}\"", .{std.zig.fmtString(str)}); + }, + .error_value => |str_index| { + const str = self.code.nullTerminatedString(str_index); + try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)}); + }, + .under => unreachable, // '_..._' is not allowed + .body_len => |body_len| { + const item_body = self.code.bodySlice(extra_index, body_len); + extra_index += item_body.len; + try self.writeBracedDecl(stream, item_body); + }, } } try stream.writeAll(" => ");