diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index ab81f343bdf1..670dbd755d28 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -7662,10 +7662,12 @@ fn switchExpr( var scalar_cases_len: u32 = 0; var multi_cases_len: u32 = 0; var inline_cases_len: u32 = 0; - var special_prong: Zir.SpecialProng = .none; - var special_node: Ast.Node.OptionalIndex = .none; + var else_case_node: Ast.Node.OptionalIndex = .none; var else_src: ?Ast.TokenIndex = null; + var underscore_case_node: Ast.Node.OptionalIndex = .none; + var underscore_node: Ast.Node.OptionalIndex = .none; var underscore_src: ?Ast.TokenIndex = null; + var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none; for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; if (case.payload_token) |payload_token| { @@ -7686,7 +7688,8 @@ fn switchExpr( any_non_inline_capture = true; } } - // Check for else/`_` prong. + + // Check for else prong. if (case.ast.values.len == 0) { const case_src = case.ast.arrow_token - 1; if (else_src) |src| { @@ -7702,79 +7705,51 @@ fn switchExpr( ), }, ); - } else if (underscore_src) |some_underscore| { - return astgen.failNodeNotes( - node, - "else and '_' prong in switch expression", - .{}, - &[_]u32{ - try astgen.errNoteTok( - case_src, - "else prong here", - .{}, - ), - try astgen.errNoteTok( - some_underscore, - "'_' prong here", - .{}, - ), - }, - ); } - special_node = case_node.toOptional(); - special_prong = .@"else"; + else_case_node = case_node.toOptional(); else_src = case_src; continue; - } else if (case.ast.values.len == 1 and - tree.nodeTag(case.ast.values[0]) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")) - { - const case_src = case.ast.arrow_token - 1; - if (underscore_src) |src| { - return astgen.failTokNotes( - case_src, - "multiple '_' prongs in switch expression", - .{}, - &[_]u32{ - try astgen.errNoteTok( - src, - "previous '_' prong here", - .{}, - ), - }, - ); - } else if (else_src) |some_else| { - return astgen.failNodeNotes( - node, - "else and '_' prong in switch expression", - .{}, - &[_]u32{ - try astgen.errNoteTok( - some_else, - "else prong here", - .{}, - ), - try astgen.errNoteTok( - case_src, - "'_' prong here", - .{}, - ), - }, - ); - } - if (case.inline_token != null) { - return astgen.failTok(case_src, "cannot inline '_' prong", .{}); - } - special_node = case_node.toOptional(); - special_prong = .under; - underscore_src = case_src; - continue; } + // Check for '_' prong. + var case_has_underscore = false; for (case.ast.values) |val| { - if (tree.nodeTag(val) == .string_literal) - return astgen.failNode(val, "cannot switch on strings", .{}); + switch (tree.nodeTag(val)) { + .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { + const val_src = tree.nodeMainToken(val); + if (underscore_src) |src| { + return astgen.failTokNotes( + val_src, + "multiple '_' prongs in switch expression", + .{}, + &[_]u32{ + try astgen.errNoteTok( + src, + "previous '_' prong here", + .{}, + ), + }, + ); + } + if (case.inline_token != null) { + return astgen.failTok(val_src, "cannot inline '_' prong", .{}); + } + underscore_case_node = case_node.toOptional(); + underscore_src = val_src; + underscore_node = val.toOptional(); + underscore_additional_items = switch (case.ast.values.len) { + 0 => unreachable, + 1 => .none, + 2 => .one, + else => .many, + }; + case_has_underscore = true; + }, + .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}), + else => {}, + } } + if (case_has_underscore) continue; if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { scalar_cases_len += 1; @@ -7786,6 +7761,14 @@ fn switchExpr( } } + const special_prongs: Zir.SpecialProngs = .init( + else_src != null, + underscore_src != null, + underscore_additional_items, + ); + const has_else = special_prongs.hasElse(); + const has_under = special_prongs.hasUnder(); + const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none }; astgen.advanceSourceCursorToNode(operand_node); @@ -7806,7 +7789,9 @@ fn switchExpr( const payloads = &astgen.scratch; const scratch_top = astgen.scratch.items.len; const case_table_start = scratch_top; - const scalar_case_table = case_table_start + @intFromBool(special_prong != .none); + const else_case_index = if (has_else) case_table_start else undefined; + const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined; + const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under); const multi_case_table = scalar_case_table + scalar_cases_len; const case_table_end = multi_case_table + multi_cases_len; try astgen.scratch.resize(gpa, case_table_end); @@ -7938,14 +7923,33 @@ fn switchExpr( const header_index: u32 = @intCast(payloads.items.len); const body_len_index = if (is_multi_case) blk: { - payloads.items[multi_case_table + multi_case_index] = header_index; - multi_case_index += 1; + if (case_node.toOptional() == underscore_case_node) { + payloads.items[under_case_index] = header_index; + if (special_prongs.hasOneAdditionalItem()) { + try payloads.resize(gpa, header_index + 2); // item, body_len + const maybe_item_node = case.ast.values[0]; + const item_node = if (maybe_item_node.toOptional() == underscore_node) + case.ast.values[1] + else + maybe_item_node; + const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); + payloads.items[header_index] = @intFromEnum(item_inst); + break :blk header_index + 1; + } + } else { + payloads.items[multi_case_table + multi_case_index] = header_index; + multi_case_index += 1; + } try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len // items var items_len: u32 = 0; for (case.ast.values) |item_node| { - if (tree.nodeTag(item_node) == .switch_range) continue; + if (item_node.toOptional() == underscore_node or + tree.nodeTag(item_node) == .switch_range) + { + continue; + } items_len += 1; const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); @@ -7955,7 +7959,9 @@ fn switchExpr( // ranges var ranges_len: u32 = 0; for (case.ast.values) |range| { - if (tree.nodeTag(range) != .switch_range) continue; + if (tree.nodeTag(range) != .switch_range) { + continue; + } ranges_len += 1; const first_node, const last_node = tree.nodeData(range).node_and_node; @@ -7969,8 +7975,13 @@ fn switchExpr( payloads.items[header_index] = items_len; payloads.items[header_index + 1] = ranges_len; break :blk header_index + 2; - } else if (case_node.toOptional() == special_node) blk: { - payloads.items[case_table_start] = header_index; + } else if (case_node.toOptional() == else_case_node) blk: { + payloads.items[else_case_index] = header_index; + try payloads.resize(gpa, header_index + 1); // body_len + break :blk header_index; + } else if (case_node.toOptional() == underscore_case_node) blk: { + assert(!special_prongs.hasAdditionalItems()); + payloads.items[under_case_index] = header_index; try payloads.resize(gpa, header_index + 1); // body_len break :blk header_index; } else blk: { @@ -8025,15 +8036,13 @@ fn switchExpr( try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + @intFromBool(multi_cases_len != 0) + @intFromBool(any_has_tag_capture) + - payloads.items.len - case_table_end + - (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len); + payloads.items.len - scratch_top); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ .operand = raw_operand, .bits = Zir.Inst.SwitchBlock.Bits{ .has_multi_cases = multi_cases_len != 0, - .has_else = special_prong == .@"else", - .has_under = special_prong == .under, + .special_prongs = special_prongs, .any_has_tag_capture = any_has_tag_capture, .any_non_inline_capture = any_non_inline_capture, .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, @@ -8052,13 +8061,41 @@ fn switchExpr( const zir_datas = astgen.instructions.items(.data); zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; - for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| { + if (has_else) { + const start_index = payloads.items[else_case_index]; + var end_index = start_index + 1; + const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]); + end_index += prong_info.body_len; + astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); + } + if (has_under) { + const start_index = payloads.items[under_case_index]; var body_len_index = start_index; var end_index = start_index; - const table_index = case_table_start + i; - if (table_index < scalar_case_table) { - end_index += 1; - } else if (table_index < multi_case_table) { + switch (underscore_additional_items) { + .none => { + end_index += 1; + }, + .one => { + body_len_index += 1; + end_index += 2; + }, + .many => { + body_len_index += 2; + const items_len = payloads.items[start_index]; + const ranges_len = payloads.items[start_index + 1]; + end_index += 3 + items_len + 2 * ranges_len; + }, + } + const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); + end_index += prong_info.body_len; + astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); + } + for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| { + var body_len_index = start_index; + var end_index = start_index; + const table_index = scalar_case_table + i; + if (table_index < multi_case_table) { body_len_index += 1; end_index += 2; } else { diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 1bb0b9d37f4e..f6781a74a248 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -3226,20 +3226,32 @@ pub const Inst = struct { /// 0. multi_cases_len: u32 // If has_multi_cases is set. /// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture. - /// 2. else_body { // If has_else or has_under is set. + /// 2. else_body { // If special_prong.hasElse() is set. /// info: ProngInfo, /// body member Index for every info.body_len /// } - /// 3. scalar_cases: { // for every scalar_cases_len + /// 3. under_body { // If special_prong.hasUnder() is set. + /// item: Ref, // If special_prong.hasOneAdditionalItem() is set. + /// items_len: u32, // If special_prong.hasManyAdditionalItems() is set. + /// ranges_len: u32, // If special_prong.hasManyAdditionalItems() is set. + /// info: ProngInfo, + /// item: Ref, // for every items_len + /// ranges: { // for every ranges_len + /// item_first: Ref, + /// item_last: Ref, + /// } + /// body member Index for every info.body_len + /// } + /// 4. scalar_cases: { // for every scalar_cases_len /// item: Ref, /// info: ProngInfo, /// body member Index for every info.body_len /// } - /// 4. multi_cases: { // for every multi_cases_len + /// 5. multi_cases: { // for every multi_cases_len /// items_len: u32, /// ranges_len: u32, /// info: ProngInfo, - /// item: Ref // for every items_len + /// item: Ref, // for every items_len /// ranges: { // for every ranges_len /// item_first: Ref, /// item_last: Ref, @@ -3275,30 +3287,18 @@ pub const Inst = struct { pub const Bits = packed struct(u32) { /// If true, one or more prongs have multiple items. has_multi_cases: bool, - /// If true, there is an else prong. This is mutually exclusive with `has_under`. - has_else: bool, - /// If true, there is an underscore prong. This is mutually exclusive with `has_else`. - has_under: bool, + /// Information about the special prong. + special_prongs: SpecialProngs, /// If true, at least one prong has an inline tag capture. any_has_tag_capture: bool, /// If true, at least one prong has a capture which may not /// be comptime-known via `inline`. any_non_inline_capture: bool, + /// If true, at least one prong contains a `continue`. has_continue: bool, scalar_cases_len: ScalarCasesLen, - pub const ScalarCasesLen = u26; - - pub fn specialProng(bits: Bits) SpecialProng { - const has_else: u2 = @intFromBool(bits.has_else); - const has_under: u2 = @intFromBool(bits.has_under); - return switch ((has_else << 1) | has_under) { - 0b00 => .none, - 0b01 => .under, - 0b10 => .@"else", - 0b11 => unreachable, - }; - } + pub const ScalarCasesLen = u25; }; pub const MultiProng = struct { @@ -3874,7 +3874,68 @@ pub const Inst = struct { }; }; -pub const SpecialProng = enum { none, @"else", under }; +pub const SpecialProngs = enum(u3) { + none = 0b000, + /// Simple `else` prong. + /// `else => {},` + @"else" = 0b001, + /// Simple `_` prong. + /// `_ => {},` + under = 0b010, + /// Both an `else` and a `_` prong. + /// `else => {},` + /// `_ => {},` + under_and_else = 0b011, + /// `_` prong with 1 additional item. + /// `a, _ => {},` + under_one_item = 0b100, + /// Both an `else` and a `_` prong with 1 additional item. + /// `else => {},` + /// `a, _ => {},` + under_one_item_and_else = 0b101, + /// `_` prong with >1 additional items. + /// `a, _, b => {},` + under_many_items = 0b110, + /// Both an `else` and a `_` prong with >1 additional items. + /// `else => {},` + /// `a, _, b => {},` + under_many_items_and_else = 0b111, + + pub const AdditionalItems = enum(u3) { + none = @intFromEnum(SpecialProngs.under), + one = @intFromEnum(SpecialProngs.under_one_item), + many = @intFromEnum(SpecialProngs.under_many_items), + }; + + pub fn init(has_else: bool, has_under: bool, additional_items: AdditionalItems) SpecialProngs { + const else_bit: u3 = @intFromBool(has_else); + const under_bits: u3 = if (has_under) + @intFromEnum(additional_items) + else + @intFromEnum(SpecialProngs.none); + return @enumFromInt(else_bit | under_bits); + } + + pub fn hasElse(special_prongs: SpecialProngs) bool { + return (@intFromEnum(special_prongs) & 0b001) != 0; + } + + pub fn hasUnder(special_prongs: SpecialProngs) bool { + return (@intFromEnum(special_prongs) & 0b110) != 0; + } + + pub fn hasAdditionalItems(special_prongs: SpecialProngs) bool { + return (@intFromEnum(special_prongs) & 0b100) != 0; + } + + pub fn hasOneAdditionalItem(special_prongs: SpecialProngs) bool { + return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_one_item); + } + + pub fn hasManyAdditionalItems(special_prongs: SpecialProngs) bool { + return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_many_items); + } +}; pub const DeclIterator = struct { extra_index: u32, @@ -4718,7 +4779,7 @@ fn findTrackableSwitch( } const has_special = switch (kind) { - .normal => extra.data.bits.specialProng() != .none, + .normal => extra.data.bits.special_prongs != .none, .err_union => has_special: { // Handle `non_err_body` first. const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); @@ -4733,12 +4794,40 @@ fn findTrackableSwitch( }; if (has_special) { - const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); - extra_index += 1; - const body = zir.bodySlice(extra_index, prong_info.body_len); - extra_index += body.len; + const has_else = if (kind == .normal) + extra.data.bits.special_prongs.hasElse() + else + true; + if (has_else) { + const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); + extra_index += 1; + const body = zir.bodySlice(extra_index, prong_info.body_len); + extra_index += body.len; - try zir.findTrackableBody(gpa, contents, defers, body); + try zir.findTrackableBody(gpa, contents, defers, body); + } + if (kind == .normal) { + const special_prongs = extra.data.bits.special_prongs; + + if (special_prongs.hasUnder()) { + var trailing_items_len: u32 = 0; + if (special_prongs.hasOneAdditionalItem()) { + extra_index += 1; + } else if (special_prongs.hasManyAdditionalItems()) { + const items_len = zir.extra[extra_index]; + extra_index += 1; + const ranges_len = zir.extra[extra_index]; + extra_index += 1; + trailing_items_len = items_len + ranges_len * 2; + } + const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); + extra_index += 1 + trailing_items_len; + const body = zir.bodySlice(extra_index, prong_info.body_len); + extra_index += body.len; + + try zir.findTrackableBody(gpa, contents, defers, body); + } + } } { diff --git a/src/Sema.zig b/src/Sema.zig index 8cfcadea66c2..ea46b0d2c03e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10928,7 +10928,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp const switch_src = block.nodeOffset(inst_data.src_node); const switch_src_node_offset = inst_data.src_node; const switch_operand_src = block.src(.{ .node_offset_switch_operand = switch_src_node_offset }); - const else_prong_src = block.src(.{ .node_offset_switch_special_prong = switch_src_node_offset }); + const else_prong_src = block.src(.{ .node_offset_switch_else_prong = switch_src_node_offset }); const extra = sema.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index); const main_operand_src = block.src(.{ .node_offset_if_cond = extra.data.main_src_node_offset }); const main_src = block.src(.{ .node_offset_main_token = extra.data.main_src_node_offset }); @@ -11122,6 +11122,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp err_val, operand_err_set_ty, switch_src_node_offset, + null, .{ .body = else_case.body, .end = else_case.end, @@ -11129,6 +11130,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp .is_inline = else_case.is_inline, .has_tag_capture = false, }, + false, case_vals, scalar_cases_len, multi_cases_len, @@ -11200,6 +11202,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp true, switch_src_node_offset, else_prong_src, + false, undefined, seen_errors, undefined, @@ -11207,6 +11210,10 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp undefined, cond_dbg_node_index, true, + null, + undefined, + &.{}, + &.{}, ); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len + @@ -11243,12 +11250,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const pt = sema.pt; const zcu = pt.zcu; + const ip = &zcu.intern_pool; const gpa = sema.gpa; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; const src = block.nodeOffset(inst_data.src_node); const src_node_offset = inst_data.src_node; const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset }); - const special_prong_src = block.src(.{ .node_offset_switch_special_prong = 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 }); const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: { @@ -11335,27 +11344,63 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); defer case_vals.deinit(gpa); - const special_prong = extra.data.bits.specialProng(); - const special: SpecialProng = switch (special_prong) { - .none => .{ - .body = &.{}, - .end = header_extra_index, - .capture = .none, - .is_inline = false, - .has_tag_capture = false, - }, - .under, .@"else" => blk: { - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]); - const extra_body_start = header_extra_index + 1; - break :blk .{ - .body = sema.code.bodySlice(extra_body_start, info.body_len), - .end = extra_body_start + info.body_len, - .capture = info.capture, - .is_inline = info.is_inline, - .has_tag_capture = info.has_tag_capture, - }; - }, + var single_absorbed_item: Zir.Inst.Ref = .none; + var absorbed_items: []const Zir.Inst.Ref = &.{}; + var absorbed_ranges: []const Zir.Inst.Ref = &.{}; + + const special_prongs = extra.data.bits.special_prongs; + const has_else = special_prongs.hasElse(); + const has_under = special_prongs.hasUnder(); + const special_else: SpecialProng = if (has_else) blk: { + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]); + const extra_body_start = header_extra_index + 1; + break :blk .{ + .body = sema.code.bodySlice(extra_body_start, info.body_len), + .end = extra_body_start + info.body_len, + .capture = info.capture, + .is_inline = info.is_inline, + .has_tag_capture = info.has_tag_capture, + }; + } else .{ + .body = &.{}, + .end = header_extra_index, + .capture = .none, + .is_inline = false, + .has_tag_capture = false, }; + const special_under: SpecialProng = if (has_under) blk: { + var extra_index = special_else.end; + var trailing_items_len: usize = 0; + if (special_prongs.hasOneAdditionalItem()) { + single_absorbed_item = @enumFromInt(sema.code.extra[extra_index]); + extra_index += 1; + absorbed_items = @ptrCast(&single_absorbed_item); + } else if (special_prongs.hasManyAdditionalItems()) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + absorbed_items = sema.code.refSlice(extra_index + 1, items_len); + absorbed_ranges = sema.code.refSlice(extra_index + 1 + items_len, ranges_len * 2); + trailing_items_len = items_len + ranges_len * 2; + } + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]); + extra_index += 1 + trailing_items_len; + break :blk .{ + .body = sema.code.bodySlice(extra_index, info.body_len), + .end = extra_index + info.body_len, + .capture = info.capture, + .is_inline = info.is_inline, + .has_tag_capture = info.has_tag_capture, + }; + } else .{ + .body = &.{}, + .end = special_else.end, + .capture = .none, + .is_inline = false, + .has_tag_capture = false, + }; + const special_end = special_under.end; // Duplicate checking variables later also used for `inline else`. var seen_enum_fields: []?LazySrcLoc = &.{}; @@ -11375,7 +11420,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r var else_error_ty: ?Type = null; // Validate usage of '_' prongs. - if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) { + if (has_under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) { const msg = msg: { const msg = try sema.errMsg( src, @@ -11384,7 +11429,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r ); errdefer msg.destroy(gpa); try sema.errNote( - special_prong_src, + under_prong_src, msg, "'_' prong here", .{}, @@ -11409,7 +11454,23 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r @memset(seen_enum_fields, null); // `range_set` is used for non-exhaustive enum values that do not correspond to any tags. - var extra_index: usize = special.end; + for (absorbed_items, 0..) |item_ref, item_i| { + _ = try sema.validateSwitchItemEnum( + block, + seen_enum_fields, + &range_set, + item_ref, + cond_ty, + block.src(.{ .switch_case_item = .{ + .switch_node_offset = src_node_offset, + .case_idx = .special_under, + .item_idx = .{ .kind = .single, .index = @intCast(item_i) }, + } }), + ); + } + try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset); + + var extra_index: usize = special_end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -11467,13 +11528,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r if (seen_src == null) break false; } else true; - if (special_prong == .@"else") { - if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail( - block, - special_prong_src, - "unreachable else prong; all cases already handled", - .{}, - ); + if (has_else) { + if (all_tags_handled) { + if (cond_ty.isNonexhaustiveEnum(zcu)) { + if (has_under) return sema.fail( + block, + else_prong_src, + "unreachable else prong; all explicit cases already handled", + .{}, + ); + } else return sema.fail( + block, + else_prong_src, + "unreachable else prong; all cases already handled", + .{}, + ); + } } else if (!all_tags_handled) { const msg = msg: { const msg = try sema.errMsg( @@ -11491,7 +11561,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r i, msg, "unhandled enumeration value: '{f}'", - .{field_name.fmt(&zcu.intern_pool)}, + .{field_name.fmt(ip)}, ); } try sema.errNote( @@ -11503,11 +11573,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r break :msg msg; }; return sema.failWithOwnedErrorMsg(block, msg); - } else if (special_prong == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) { + } else if (special_prongs == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) { return sema.fail( block, src, - "switch on non-exhaustive enum must include 'else' or '_' prong", + "switch on non-exhaustive enum must include 'else' or '_' prong or both", .{}, ); } @@ -11521,11 +11591,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r inst_data, scalar_cases_len, multi_cases_len, - .{ .body = special.body, .end = special.end, .src = special_prong_src }, - special_prong == .@"else", + .{ .body = special_else.body, .end = special_else.end, .src = else_prong_src }, + has_else, ), .int, .comptime_int => { - var extra_index: usize = special.end; + var extra_index: usize = special_end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -11607,10 +11677,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const min_int = try cond_ty.minInt(pt, cond_ty); const max_int = try cond_ty.maxInt(pt, cond_ty); if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) { - if (special_prong == .@"else") { + if (has_else) { return sema.fail( block, - special_prong_src, + else_prong_src, "unreachable else prong; all cases already handled", .{}, ); @@ -11618,7 +11688,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r break :check_range; } } - if (special_prong != .@"else") { + if (special_prongs == .none) { return sema.fail( block, src, @@ -11629,7 +11699,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r } }, .bool => { - var extra_index: usize = special.end; + var extra_index: usize = special_end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -11681,31 +11751,28 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset); } } - switch (special_prong) { - .@"else" => { - if (true_count + false_count == 2) { - return sema.fail( - block, - special_prong_src, - "unreachable else prong; all cases already handled", - .{}, - ); - } - }, - .under, .none => { - if (true_count + false_count < 2) { - return sema.fail( - block, - src, - "switch must handle all possibilities", - .{}, - ); - } - }, + if (has_else) { + if (true_count + false_count == 2) { + return sema.fail( + block, + else_prong_src, + "unreachable else prong; all cases already handled", + .{}, + ); + } + } else { + if (true_count + false_count < 2) { + return sema.fail( + block, + src, + "switch must handle all possibilities", + .{}, + ); + } } }, .enum_literal, .void, .@"fn", .pointer, .type => { - if (special_prong != .@"else") { + if (!has_else) { return sema.fail( block, src, @@ -11717,7 +11784,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r var seen_values = ValueSrcMap{}; defer seen_values.deinit(gpa); - var extra_index: usize = special.end; + var extra_index: usize = special_end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -11790,6 +11857,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r }), } + var special_members_only: ?SpecialProng = null; + var special_members_only_src: LazySrcLoc = undefined; + const special_generic, const special_generic_src = if (has_under) b: { + if (has_else) { + special_members_only = special_else; + special_members_only_src = else_prong_src; + } + break :b .{ special_under, under_prong_src }; + } else .{ special_else, else_prong_src }; + const spa: SwitchProngAnalysis = .{ .sema = sema, .parent_block = block, @@ -11836,11 +11913,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r defer child_block.instructions.deinit(gpa); defer merges.deinit(gpa); - if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) { + if (scalar_cases_len + multi_cases_len == 0 and + special_members_only == null and + !special_generic.is_inline) + { if (empty_enum) { return .void_value; } - if (special_prong == .none) { + if (special_prongs == .none) { return sema.fail(block, src, "switch must handle all possibilities", .{}); } const init_cond = switch (operand) { @@ -11854,7 +11934,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r const ok = try block.addUnOp(.is_named_enum_value, init_cond); try sema.addSafetyCheck(block, src, ok, .corrupt_switch); } - if (err_set and try sema.maybeErrorUnwrap(block, special.body, init_cond, operand_src, false)) { + if (err_set and try sema.maybeErrorUnwrap(block, special_generic.body, init_cond, operand_src, false)) { return .unreachable_value; } } @@ -11874,7 +11954,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r cond_ty, cond_val, src_node_offset, - special, + special_members_only, + special_generic, + has_under, case_vals, scalar_cases_len, multi_cases_len, @@ -11884,15 +11966,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r ); } - if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline and !extra.data.bits.has_continue) { + if (scalar_cases_len + multi_cases_len == 0 and + special_members_only == null and + !special_generic.is_inline and + !extra.data.bits.has_continue) + { return spa.resolveProngComptime( &child_block, .special, - special.body, - special.capture, + special_generic.body, + special_generic.capture, block.src(.{ .switch_capture = .{ .switch_node_offset = src_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = if (has_under) .special_under else .special_else, } }), undefined, // case_vals may be undefined for special prongs .none, @@ -11908,6 +11994,87 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r unreachable; } + var extra_case_vals: struct { + items: std.ArrayListUnmanaged(Air.Inst.Ref), + ranges: std.ArrayListUnmanaged([2]Air.Inst.Ref), + } = .{ .items = .empty, .ranges = .empty }; + defer { + extra_case_vals.items.deinit(gpa); + extra_case_vals.ranges.deinit(gpa); + } + + // Runtime switch, if we have a special_members_only prong we need to unroll + // it to a prong with explicit items. + // Although this is potentially the same as `inline else` it does not count + // towards the backward branch quota because it's an implementation detail. + if (special_members_only != null) gen: { + assert(cond_ty.isNonexhaustiveEnum(zcu)); + + var min_i: usize = math.maxInt(usize); + var max_i: usize = 0; + var seen_field_count: usize = 0; + for (seen_enum_fields, 0..) |seen, enum_i| { + if (seen != null) { + seen_field_count += 1; + } else { + min_i = @min(min_i, enum_i); + max_i = @max(max_i, enum_i); + } + } + if (min_i == max_i) { + seen_enum_fields[min_i] = special_members_only_src; + const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i)); + const item_ref = Air.internedToRef(item_val.toIntern()); + try extra_case_vals.items.append(gpa, item_ref); + break :gen; + } + const missing_field_count = seen_enum_fields.len - seen_field_count; + + extra_case_vals.items = try .initCapacity(gpa, missing_field_count / 2); + extra_case_vals.ranges = try .initCapacity(gpa, missing_field_count / 4); + const int_ty = cond_ty.intTagType(zcu); + + var last_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i)); + var first_ref = Air.internedToRef(last_val.toIntern()); + seen_enum_fields[min_i] = special_members_only_src; + for (seen_enum_fields[(min_i + 1)..(max_i + 1)], (min_i + 1)..) |seen, enum_i| { + if (seen != null) continue; + seen_enum_fields[enum_i] = special_members_only_src; + + const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(enum_i)); + const item_ref = Air.internedToRef(item_val.toIntern()); + + const is_next = is_next: { + const prev_int = ip.indexToKey(last_val.toIntern()).enum_tag.int; + + const result = try arith.incrementDefinedInt(sema, int_ty, .fromInterned(prev_int)); + if (result.overflow) break :is_next false; + + const item_int = ip.indexToKey(item_val.toIntern()).enum_tag.int; + break :is_next try sema.valuesEqual(.fromInterned(item_int), result.val, int_ty); + }; + + if (is_next) { + last_val = item_val; + } else { + const last_ref = Air.internedToRef(last_val.toIntern()); + if (first_ref == last_ref) { + try extra_case_vals.items.append(gpa, first_ref); + } else { + try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref }); + } + first_ref = item_ref; + last_val = item_val; + } + } + const last_ref = Air.internedToRef(last_val.toIntern()); + if (first_ref == last_ref) { + try extra_case_vals.items.append(gpa, first_ref); + } else { + try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref }); + } + } + const air_switch_ref = try sema.analyzeSwitchRuntimeBlock( spa, &child_block, @@ -11919,14 +12086,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r cond_ty, operand_src, case_vals, - special, + special_generic, scalar_cases_len, multi_cases_len, union_originally, raw_operand_ty, err_set, src_node_offset, - special_prong_src, + special_generic_src, + has_under, seen_enum_fields, seen_errors, range_set, @@ -11934,6 +12102,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r false_count, cond_dbg_node_index, false, + special_members_only, + special_members_only_src, + extra_case_vals.items.items, + extra_case_vals.ranges.items, ); for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| { @@ -12017,14 +12189,15 @@ fn analyzeSwitchRuntimeBlock( operand_ty: Type, operand_src: LazySrcLoc, case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), - special: SpecialProng, + else_prong: SpecialProng, scalar_cases_len: usize, multi_cases_len: usize, union_originally: bool, maybe_union_ty: Type, err_set: bool, switch_node_offset: std.zig.Ast.Node.Offset, - special_prong_src: LazySrcLoc, + else_prong_src: LazySrcLoc, + else_prong_is_underscore: bool, seen_enum_fields: []?LazySrcLoc, seen_errors: SwitchErrorSet, range_set: RangeSet, @@ -12032,6 +12205,11 @@ fn analyzeSwitchRuntimeBlock( false_count: u8, cond_dbg_node_index: Zir.Inst.Index, allow_err_code_unwrap: bool, + extra_prong: ?SpecialProng, + /// May be `undefined` if `extra_prong` is `null` + extra_prong_src: LazySrcLoc, + extra_prong_items: []const Air.Inst.Ref, + extra_prong_ranges: []const [2]Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const pt = sema.pt; const zcu = pt.zcu; @@ -12055,7 +12233,7 @@ fn analyzeSwitchRuntimeBlock( case_block.need_debug_scope = null; // this body is emitted regardless defer case_block.instructions.deinit(gpa); - var extra_index: usize = special.end; + var extra_index: usize = else_prong.end; var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -12117,23 +12295,42 @@ fn analyzeSwitchRuntimeBlock( var cases_len = scalar_cases_len; var case_val_idx: usize = scalar_cases_len; + const multi_cases_len_with_extra_prong = multi_cases_len + @intFromBool(extra_prong != null); var multi_i: u32 = 0; - while (multi_i < multi_cases_len) : (multi_i += 1) { - const items_len = sema.code.extra[extra_index]; - extra_index += 1; - const ranges_len = sema.code.extra[extra_index]; - extra_index += 1; - const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]); - extra_index += 1 + items_len + 2 * ranges_len; + while (multi_i < multi_cases_len_with_extra_prong) : (multi_i += 1) { + const is_extra_prong = multi_i == multi_cases_len; + var items: []const Air.Inst.Ref = undefined; + var info: Zir.Inst.SwitchBlock.ProngInfo = undefined; + var ranges: []const [2]Air.Inst.Ref = undefined; + var body: []const Zir.Inst.Index = undefined; + if (is_extra_prong) { + const prong = extra_prong.?; + items = extra_prong_items; + ranges = extra_prong_ranges; + body = prong.body; + info = .{ + .body_len = undefined, + .capture = prong.capture, + .is_inline = prong.is_inline, + .has_tag_capture = prong.has_tag_capture, + }; + } else { + @branchHint(.likely); + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + info = @bitCast(sema.code.extra[extra_index]); + extra_index += 1 + items_len + ranges_len * 2; - const items = case_vals.items[case_val_idx..][0..items_len]; - case_val_idx += items_len; - // TODO: @ptrCast slice once Sema supports it - const ranges: []const [2]Air.Inst.Ref = @as([*]const [2]Air.Inst.Ref, @ptrCast(case_vals.items[case_val_idx..]))[0..ranges_len]; - case_val_idx += ranges_len * 2; + items = case_vals.items[case_val_idx..][0..items_len]; + case_val_idx += items_len; + ranges = @ptrCast(case_vals.items[case_val_idx..][0 .. ranges_len * 2]); + case_val_idx += ranges_len * 2; - const body = sema.code.bodySlice(extra_index, info.body_len); - extra_index += info.body_len; + body = sema.code.bodySlice(extra_index, info.body_len); + extra_index += info.body_len; + } case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; @@ -12143,14 +12340,29 @@ fn analyzeSwitchRuntimeBlock( var emit_bb = false; for (ranges, 0..) |range_items, range_i| { - var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable; - const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable; + var item = sema.resolveConstDefinedValue(block, .unneeded, range_items[0], undefined) catch unreachable; + const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_items[1], undefined) catch unreachable; while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({ // Previous validation has resolved any possible lazy values. - const result = try arith.incrementDefinedInt(sema, operand_ty, item); + const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) { + .int => .{ item, operand_ty }, + .@"enum" => b: { + const int_val = Value.fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int); + break :b .{ int_val, int_val.typeOf(zcu) }; + }, + else => unreachable, + }; + const result = try arith.incrementDefinedInt(sema, int_ty, int_val); assert(!result.overflow); - item = result.val; + item = switch (operand_ty.zigTypeTag(zcu)) { + .int => result.val, + .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{ + .ty = operand_ty.toIntern(), + .int = result.val.toIntern(), + } })), + else => unreachable, + }; }) { cases_len += 1; @@ -12159,11 +12371,14 @@ fn analyzeSwitchRuntimeBlock( case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; - if (emit_bb) try sema.emitBackwardBranch(block, block.src(.{ .switch_case_item = .{ - .switch_node_offset = switch_node_offset, - .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, - .item_idx = .{ .kind = .range, .index = @intCast(range_i) }, - } })); + if (emit_bb) { + const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{ + .switch_node_offset = switch_node_offset, + .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, + .item_idx = .{ .kind = .range, .index = @intCast(range_i) }, + } }); + try sema.emitBackwardBranch(block, bb_src); + } emit_bb = true; const prong_hint = try spa.analyzeProngRuntime( @@ -12208,11 +12423,14 @@ fn analyzeSwitchRuntimeBlock( break :blk field_ty.zigTypeTag(zcu) != .noreturn; } else true; - if (emit_bb) try sema.emitBackwardBranch(block, block.src(.{ .switch_case_item = .{ - .switch_node_offset = switch_node_offset, - .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, - .item_idx = .{ .kind = .single, .index = @intCast(item_i) }, - } })); + if (emit_bb) { + const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{ + .switch_node_offset = switch_node_offset, + .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, + .item_idx = .{ .kind = .single, .index = @intCast(item_i) }, + } }); + try sema.emitBackwardBranch(block, bb_src); + } emit_bb = true; const prong_hint: std.builtin.BranchHint = if (analyze_body) h: { @@ -12288,11 +12506,11 @@ fn analyzeSwitchRuntimeBlock( try branch_hints.append(gpa, prong_hint); try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + - items.len + 2 * ranges_len + + items.len + ranges.len * 2 + case_block.instructions.items.len); cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{ .items_len = @intCast(items.len), - .ranges_len = @intCast(ranges_len), + .ranges_len = @intCast(ranges.len), .body_len = @intCast(case_block.instructions.items.len), })); @@ -12309,12 +12527,14 @@ fn analyzeSwitchRuntimeBlock( cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); } - const else_body: []const Air.Inst.Index = if (special.body.len != 0 or case_block.wantSafety()) else_body: { + const else_body: []const Air.Inst.Index = if (else_prong.body.len != 0 or case_block.wantSafety()) else_body: { var emit_bb = false; - if (special.is_inline) switch (operand_ty.zigTypeTag(zcu)) { + // If this is true we must have a 'true' else prong and not an underscore because + // underscore prongs can never be inlined. We've already checked for this. + if (else_prong.is_inline) switch (operand_ty.zigTypeTag(zcu)) { .@"enum" => { if (operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) { - return sema.fail(block, special_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ + return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ operand_ty.fmt(pt), }); } @@ -12333,22 +12553,22 @@ fn analyzeSwitchRuntimeBlock( break :blk field_ty.zigTypeTag(zcu) != .noreturn; } else true; - if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); emit_bb = true; const prong_hint: std.builtin.BranchHint = if (analyze_body) h: { break :h try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = .special_else, } }), &.{item_ref}, item_ref, - special.has_tag_capture, + else_prong.has_tag_capture, ); } else h: { _ = try case_block.addNoOp(.unreach); @@ -12370,7 +12590,7 @@ fn analyzeSwitchRuntimeBlock( }, .error_set => { if (operand_ty.isAnyError(zcu)) { - return sema.fail(block, special_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ + return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ operand_ty.fmt(pt), }); } @@ -12389,21 +12609,21 @@ fn analyzeSwitchRuntimeBlock( case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; - if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); emit_bb = true; const prong_hint = try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = .special_else, } }), &.{item_ref}, item_ref, - special.has_tag_capture, + else_prong.has_tag_capture, ); try branch_hints.append(gpa, prong_hint); @@ -12429,21 +12649,21 @@ fn analyzeSwitchRuntimeBlock( case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; - if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); emit_bb = true; const prong_hint = try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = .special_else, } }), &.{item_ref}, item_ref, - special.has_tag_capture, + else_prong.has_tag_capture, ); try branch_hints.append(gpa, prong_hint); @@ -12466,21 +12686,21 @@ fn analyzeSwitchRuntimeBlock( case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; - if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); emit_bb = true; const prong_hint = try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = .special_else, } }), &.{.bool_true}, .bool_true, - special.has_tag_capture, + else_prong.has_tag_capture, ); try branch_hints.append(gpa, prong_hint); @@ -12501,21 +12721,21 @@ fn analyzeSwitchRuntimeBlock( case_block.instructions.shrinkRetainingCapacity(0); case_block.error_return_trace_index = child_block.error_return_trace_index; - if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); emit_bb = true; const prong_hint = try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = .special_else, } }), &.{.bool_false}, .bool_false, - special.has_tag_capture, + else_prong.has_tag_capture, ); try branch_hints.append(gpa, prong_hint); @@ -12531,7 +12751,7 @@ fn analyzeSwitchRuntimeBlock( cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); } }, - else => return sema.fail(block, special_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ + else => return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ operand_ty.fmt(pt), }), }; @@ -12540,7 +12760,7 @@ fn analyzeSwitchRuntimeBlock( case_block.error_return_trace_index = child_block.error_return_trace_index; if (zcu.backendSupportsFeature(.is_named_enum_value) and - special.body.len != 0 and block.wantSafety() and + else_prong.body.len != 0 and block.wantSafety() and operand_ty.zigTypeTag(zcu) == .@"enum" and (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) { @@ -12549,7 +12769,12 @@ fn analyzeSwitchRuntimeBlock( try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch); } - const analyze_body = if (union_originally and !special.is_inline) + const else_src_idx: LazySrcLoc.Offset.SwitchCaseIndex = if (else_prong_is_underscore) + .special_under + else + .special_else; + + const analyze_body = if (union_originally and !else_prong.is_inline) for (seen_enum_fields, 0..) |seen_field, index| { if (seen_field != null) continue; const union_obj = zcu.typeToUnion(maybe_union_ty).?; @@ -12558,20 +12783,20 @@ fn analyzeSwitchRuntimeBlock( } else false else true; - const else_hint: std.builtin.BranchHint = if (special.body.len != 0 and err_set and - try sema.maybeErrorUnwrap(&case_block, special.body, operand, operand_src, allow_err_code_unwrap)) + const else_hint: std.builtin.BranchHint = if (else_prong.body.len != 0 and err_set and + try sema.maybeErrorUnwrap(&case_block, else_prong.body, operand, operand_src, allow_err_code_unwrap)) h: { // nothing to do here. weight against error branch break :h .unlikely; - } else if (special.body.len != 0 and analyze_body and !special.is_inline) h: { + } else if (else_prong.body.len != 0 and analyze_body and !else_prong.is_inline) h: { break :h try spa.analyzeProngRuntime( &case_block, .special, - special.body, - special.capture, + else_prong.body, + else_prong.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = else_src_idx, } }), undefined, // case_vals may be undefined for special prongs .none, @@ -12645,7 +12870,9 @@ fn resolveSwitchComptimeLoop( cond_ty: Type, init_cond_val: Value, switch_node_offset: std.zig.Ast.Node.Offset, - special: SpecialProng, + special_members_only: ?SpecialProng, + special_generic: SpecialProng, + special_generic_is_under: bool, case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), scalar_cases_len: u32, multi_cases_len: u32, @@ -12665,7 +12892,9 @@ fn resolveSwitchComptimeLoop( cond_val, cond_ty, switch_node_offset, - special, + special_members_only, + special_generic, + special_generic_is_under, case_vals, scalar_cases_len, multi_cases_len, @@ -12713,17 +12942,20 @@ fn resolveSwitchComptime( operand_val: Value, operand_ty: Type, switch_node_offset: std.zig.Ast.Node.Offset, - special: SpecialProng, + special_members_only: ?SpecialProng, + special_generic: SpecialProng, + special_generic_is_under: bool, case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), scalar_cases_len: u32, multi_cases_len: u32, err_set: bool, empty_enum: bool, ) CompileError!Air.Inst.Ref { + const zcu = sema.pt.zcu; const merges = &child_block.label.?.merges; const resolved_operand_val = try sema.resolveLazyValue(operand_val); - var extra_index: usize = special.end; + var extra_index: usize = special_generic.end; { var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { @@ -12824,23 +13056,45 @@ fn resolveSwitchComptime( extra_index += info.body_len; } } - if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special.body, cond_operand); + if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special_generic.body, cond_operand); if (empty_enum) { return .void_value; } + if (special_members_only) |special| { + assert(operand_ty.isNonexhaustiveEnum(zcu)); + if (operand_ty.enumTagFieldIndex(operand_val, zcu)) |_| { + return spa.resolveProngComptime( + child_block, + .special, + special.body, + special.capture, + child_block.src(.{ .switch_capture = .{ + .switch_node_offset = switch_node_offset, + .case_idx = .special_else, + } }), + undefined, // case_vals may be undefined for special prongs + if (special.is_inline) cond_operand else .none, + special.has_tag_capture, + merges, + ); + } + } return spa.resolveProngComptime( child_block, .special, - special.body, - special.capture, + special_generic.body, + special_generic.capture, child_block.src(.{ .switch_capture = .{ .switch_node_offset = switch_node_offset, - .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, + .case_idx = if (special_generic_is_under) + .special_under + else + .special_else, } }), undefined, // case_vals may be undefined for special prongs - if (special.is_inline) cond_operand else .none, - special.has_tag_capture, + if (special_generic.is_inline) cond_operand else .none, + special_generic.has_tag_capture, merges, ); } diff --git a/src/Zcu.zig b/src/Zcu.zig index d77a6edc310c..62af251a7ce0 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -1677,20 +1677,37 @@ pub const SrcLoc = struct { return tree.nodeToSpan(condition); }, - .node_offset_switch_special_prong => |node_off| { + .node_offset_switch_else_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).?; - const is_special = (case.ast.values.len == 0) or - (case.ast.values.len == 1 and - tree.nodeTag(case.ast.values[0]) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")); - if (!is_special) continue; + if (case.ast.values.len == 0) { + return tree.nodeToSpan(case_node); + } + } else unreachable; + }, - return tree.nodeToSpan(case_node); + .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; }, @@ -1701,12 +1718,6 @@ pub const SrcLoc = struct { 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).?; - const is_special = (case.ast.values.len == 0) or - (case.ast.values.len == 1 and - tree.nodeTag(case.ast.values[0]) == .identifier and - mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")); - if (is_special) continue; - for (case.ast.values) |item_node| { if (tree.nodeTag(item_node) == .switch_range) { return tree.nodeToSpan(item_node); @@ -2111,28 +2122,35 @@ pub const SrcLoc = struct { var multi_i: u32 = 0; var scalar_i: u32 = 0; - const case = for (case_nodes) |case_node| { + var underscore_node: Ast.Node.OptionalIndex = .none; + const case = case: for (case_nodes) |case_node| { const case = tree.fullSwitchCase(case_node).?; - const is_special = special: { - if (case.ast.values.len == 0) break :special true; - if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .identifier) { - break :special mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"); + if (case.ast.values.len == 0) { + if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_else) { + break :case case; } - break :special false; - }; - if (is_special) { - if (want_case_idx.isSpecial()) { - break case; - } - continue; + continue :case; } + if (underscore_node == .none) for (case.ast.values) |val_node| { + if (tree.nodeTag(val_node) == .identifier and + mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val_node)), "_")) + { + underscore_node = val_node.toOptional(); + if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_under) { + break :case case; + } + continue :case; + } + }; const is_multi = case.ast.values.len != 1 or tree.nodeTag(case.ast.values[0]) == .switch_range; switch (want_case_idx.kind) { - .scalar => if (!is_multi and want_case_idx.index == scalar_i) break case, - .multi => if (is_multi and want_case_idx.index == multi_i) break case, + .scalar => if (!is_multi and want_case_idx.index == scalar_i) + break :case case, + .multi => if (is_multi and want_case_idx.index == multi_i) + break :case case, } if (is_multi) { @@ -2146,7 +2164,10 @@ pub const SrcLoc = struct { .switch_case_item, .switch_case_item_range_first, .switch_case_item_range_last, - => |x| x.item_idx, + => |x| item_idx: { + assert(want_case_idx != LazySrcLoc.Offset.SwitchCaseIndex.special_else); + break :item_idx x.item_idx; + }, .switch_capture, .switch_tag_capture => { const start = switch (src_loc.lazy) { .switch_capture => case.payload_token.?, @@ -2171,7 +2192,11 @@ pub const SrcLoc = struct { .single => { var item_i: u32 = 0; for (case.ast.values) |item_node| { - if (tree.nodeTag(item_node) == .switch_range) continue; + if (item_node.toOptional() == underscore_node or + tree.nodeTag(item_node) == .switch_range) + { + continue; + } if (item_i != want_item.index) { item_i += 1; continue; @@ -2182,7 +2207,9 @@ pub const SrcLoc = struct { .range => { var range_i: u32 = 0; for (case.ast.values) |item_node| { - if (tree.nodeTag(item_node) != .switch_range) continue; + if (tree.nodeTag(item_node) != .switch_range) { + continue; + } if (range_i != want_item.index) { range_i += 1; continue; @@ -2361,10 +2388,14 @@ 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 operand. node_offset_switch_operand: Ast.Node.Offset, - /// The source location points to the else/`_` prong of a switch expression, found + /// The source location points to the else 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 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 else/`_` prong. - node_offset_switch_special_prong: Ast.Node.Offset, + /// 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 @@ -2560,10 +2591,8 @@ pub const LazySrcLoc = struct { kind: enum(u1) { scalar, multi }, index: u31, - pub const special: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32))); - pub fn isSpecial(idx: SwitchCaseIndex) bool { - return @as(u32, @bitCast(idx)) == @as(u32, @bitCast(special)); - } + pub const special_else: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32))); + pub const special_under: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32) - 1)); }; pub const SwitchItemIndex = packed struct(u32) { diff --git a/src/print_zir.zig b/src/print_zir.zig index ae674e43e5a6..846d2ac6b215 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -2087,15 +2087,10 @@ const Writer = struct { self.indent += 2; - else_prong: { - const special_prong = extra.data.bits.specialProng(); - const prong_name = switch (special_prong) { - .@"else" => "else", - .under => "_", - else => break :else_prong, - }; + const special_prongs = extra.data.bits.special_prongs; - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); + if (special_prongs.hasElse()) { + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); const capture_text = switch (info.capture) { .none => "", .by_val => "by_val ", @@ -2108,7 +2103,63 @@ const Writer = struct { try stream.writeAll(",\n"); try stream.splatByteAll(' ', self.indent); - try stream.print("{s}{s}{s} => ", .{ capture_text, inline_text, prong_name }); + try stream.print("{s}{s}else => ", .{ capture_text, inline_text }); + try self.writeBracedBody(stream, body); + } + + if (special_prongs.hasUnder()) { + var single_item_ref: Zir.Inst.Ref = .none; + var items_len: u32 = 0; + var ranges_len: u32 = 0; + if (special_prongs.hasOneAdditionalItem()) { + single_item_ref = @enumFromInt(self.code.extra[extra_index]); + extra_index += 1; + } else if (special_prongs.hasManyAdditionalItems()) { + items_len = self.code.extra[extra_index]; + extra_index += 1; + ranges_len = self.code.extra[extra_index]; + extra_index += 1; + } + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); + extra_index += 1; + const items = self.code.refSlice(extra_index, items_len); + extra_index += items_len; + + try stream.writeAll(",\n"); + try stream.splatByteAll(' ', self.indent); + switch (info.capture) { + .none => {}, + .by_val => try stream.writeAll("by_val "), + .by_ref => try stream.writeAll("by_ref "), + } + if (info.is_inline) try stream.writeAll("inline "); + + try stream.writeAll("_"); + if (single_item_ref != .none) { + try stream.writeAll(", "); + try self.writeInstRef(stream, single_item_ref); + } + for (items) |item_ref| { + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + } + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); + extra_index += 1; + const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_first); + try stream.writeAll("..."); + try self.writeInstRef(stream, item_last); + } + + const body = self.code.bodySlice(extra_index, info.body_len); + extra_index += info.body_len; + try stream.writeAll(" => "); try self.writeBracedBody(stream, body); } @@ -2116,9 +2167,9 @@ const Writer = struct { const scalar_cases_len = extra.data.bits.scalar_cases_len; var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { - const item_ref = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); + const item_ref: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); extra_index += 1; - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); extra_index += 1; const body = self.code.bodySlice(extra_index, info.body_len); extra_index += info.body_len; @@ -2143,7 +2194,7 @@ const Writer = struct { extra_index += 1; const ranges_len = self.code.extra[extra_index]; extra_index += 1; - const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index])); + const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]); extra_index += 1; const items = self.code.refSlice(extra_index, items_len); extra_index += items_len; @@ -2164,9 +2215,9 @@ const Writer = struct { var range_i: usize = 0; while (range_i < ranges_len) : (range_i += 1) { - const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); + const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); extra_index += 1; - const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index])); + const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]); extra_index += 1; if (range_i != 0 or items.len != 0) { diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index afc38661c313..b5540664c947 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -1073,3 +1073,50 @@ test "switch on 8-bit mod result" { else => unreachable, } } + +test "switch on non-exhaustive enum" { + if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO + + const E = enum(u4) { + a, + b, + c, + _, + + fn doTheTest(e: @This()) !void { + switch (e) { + .a, .b => {}, + else => return error.TestFailed, + } + switch (e) { + .a, .b => {}, + .c => return error.TestFailed, + _ => return error.TestFailed, + } + switch (e) { + .a, .b => {}, + .c, _ => return error.TestFailed, + } + switch (e) { + .a => {}, + .b, .c, _ => return error.TestFailed, + } + switch (e) { + .b => return error.TestFailed, + else => {}, + _ => return error.TestFailed, + } + switch (e) { + else => {}, + _ => return error.TestFailed, + } + switch (e) { + inline else => {}, + _ => 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 d2e967e4c774..1d82957d396f 100644 --- a/test/behavior/switch_loop.zig +++ b/test/behavior/switch_loop.zig @@ -249,3 +249,27 @@ test "switch loop on larger than pointer integer" { } try expect(entry == 3); } + +test "switch loop on non-exhaustive enum" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + 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; // TODO + + const S = struct { + const E = enum(u8) { a, b, c, _ }; + + fn doTheTest() !void { + var start: E = undefined; + start = .a; + const result: u32 = s: switch (start) { + .a => continue :s .c, + else => continue :s @enumFromInt(123), + .b, _ => |x| break :s @intFromEnum(x), + }; + try expect(result == 123); + } + }; + try S.doTheTest(); + try comptime S.doTheTest(); +} diff --git a/test/cases/compile_errors/switch_expression-non_exhaustive_absorbing.zig b/test/cases/compile_errors/switch_expression-non_exhaustive_absorbing.zig new file mode 100644 index 000000000000..cf653351c1cb --- /dev/null +++ b/test/cases/compile_errors/switch_expression-non_exhaustive_absorbing.zig @@ -0,0 +1,31 @@ +const E = enum(u8) { + a, + b, + _, +}; +const U = union(E) { + a: i32, + b: u32, +}; +pub export fn entry1() void { + const e: E = .b; + switch (e) { // error: switch not handling the tag `b` + .a, _ => {}, + } +} +pub export fn entry2() void { + const u = U{ .a = 2 }; + switch (u) { // error: `_` prong not allowed when switching on tagged union + .a => {}, + .b, _ => {}, + } +} + +// error +// +// :12:5: error: switch must handle all possibilities +// :3:5: note: unhandled enumeration value: 'b' +// :1:11: note: enum 'tmp.E' declared here +// :18:5: error: '_' prong only allowed when switching on non-exhaustive enums +// :20:13: note: '_' prong here +// :18:5: note: consider using 'else' diff --git a/test/cases/compile_errors/switch_expression-non_exhaustive_inline.zig b/test/cases/compile_errors/switch_expression-non_exhaustive_inline.zig new file mode 100644 index 000000000000..303b762171dd --- /dev/null +++ b/test/cases/compile_errors/switch_expression-non_exhaustive_inline.zig @@ -0,0 +1,25 @@ +const E = enum(u8) { + a, + b, + _, +}; + +export fn f(e: E) void { + switch (e) { + .a => {}, + inline _ => {}, + } +} + +export fn g(e: E) void { + switch (e) { + .a => {}, + else => {}, + inline _ => {}, + } +} + +// error +// +// :10:16: error: cannot inline '_' prong +// :18:16: error: cannot inline '_' prong diff --git a/test/cases/compile_errors/switch_expression-non_exhaustive_unreachable_else.zig b/test/cases/compile_errors/switch_expression-non_exhaustive_unreachable_else.zig new file mode 100644 index 000000000000..3b681c6c8ac1 --- /dev/null +++ b/test/cases/compile_errors/switch_expression-non_exhaustive_unreachable_else.zig @@ -0,0 +1,16 @@ +const E = enum(u8) { + a, + b, + _, +}; + +export fn f(e: E) void { + switch (e) { + .a, .b, _ => {}, + else => {}, + } +} + +// error +// +// :10:14: error: unreachable else prong; all explicit cases already handled diff --git a/test/cases/compile_errors/switching_with_exhaustive_enum_has___prong_.zig b/test/cases/compile_errors/switching_with_exhaustive_enum_has___prong_.zig index 2ca3f0be24c6..fdcf0f0b7c49 100644 --- a/test/cases/compile_errors/switching_with_exhaustive_enum_has___prong_.zig +++ b/test/cases/compile_errors/switching_with_exhaustive_enum_has___prong_.zig @@ -16,5 +16,5 @@ pub export fn entry() void { // target=native // // :7:5: error: '_' prong only allowed when switching on non-exhaustive enums -// :10:11: note: '_' prong here +// :10:9: note: '_' prong here // :7:5: note: consider using 'else' diff --git a/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig b/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig index ea6f2c9fd337..54aead839308 100644 --- a/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig +++ b/test/cases/compile_errors/switching_with_non-exhaustive_enums.zig @@ -37,7 +37,7 @@ pub export fn entry3() void { // :12:5: error: switch must handle all possibilities // :3:5: note: unhandled enumeration value: 'b' // :1:11: note: enum 'tmp.E' declared here -// :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong +// :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong or both // :26:5: error: '_' prong only allowed when switching on non-exhaustive enums -// :29:11: note: '_' prong here +// :29:9: note: '_' prong here // :26:5: note: consider using 'else'