Add support for both '_' and 'else' prongs at the same time in switch statements

If both are used, 'else' handles named members and '_' handles
unnamed members. In this case the 'else' prong will be unrolled
to an explicit case containing all remaining named values.
This commit is contained in:
Justus Klausecker 2025-07-10 01:58:02 +02:00
parent 1d9b1c0212
commit ba549a7d67
11 changed files with 770 additions and 364 deletions

View File

@ -2877,24 +2877,6 @@ pub const full = struct {
arrow_token: TokenIndex, arrow_token: TokenIndex,
target_expr: Node.Index, target_expr: Node.Index,
}; };
/// Returns:
/// `null` if case is not special
/// `.none` if case is else prong
/// Index of underscore otherwise
pub fn isSpecial(case: *const SwitchCase, tree: *const Ast) ?Node.OptionalIndex {
if (case.ast.values.len == 0) {
return .none;
}
for (case.ast.values) |val| {
if (tree.nodeTag(val) == .identifier and
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
{
return val.toOptional();
}
}
return null;
}
}; };
pub const Asm = struct { pub const Asm = struct {

View File

@ -7662,11 +7662,12 @@ fn switchExpr(
var scalar_cases_len: u32 = 0; var scalar_cases_len: u32 = 0;
var multi_cases_len: u32 = 0; var multi_cases_len: u32 = 0;
var inline_cases_len: u32 = 0; var inline_cases_len: u32 = 0;
var special_prong: Zir.SpecialProng = .none; var else_case_node: Ast.Node.OptionalIndex = .none;
var special_node: Ast.Node.OptionalIndex = .none;
var else_src: ?Ast.TokenIndex = null; var else_src: ?Ast.TokenIndex = null;
var underscore_src: ?Ast.TokenIndex = null; var underscore_case_node: Ast.Node.OptionalIndex = .none;
var underscore_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| { for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?; const case = tree.fullSwitchCase(case_node).?;
if (case.payload_token) |payload_token| { if (case.payload_token) |payload_token| {
@ -7687,6 +7688,7 @@ fn switchExpr(
any_non_inline_capture = true; any_non_inline_capture = true;
} }
} }
// Check for else prong. // Check for else prong.
if (case.ast.values.len == 0) { if (case.ast.values.len == 0) {
const case_src = case.ast.arrow_token - 1; const case_src = case.ast.arrow_token - 1;
@ -7703,40 +7705,21 @@ 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(); else_case_node = case_node.toOptional();
special_prong = .@"else";
else_src = case_src; else_src = case_src;
continue; continue;
} }
// Check for '_' prong. // Check for '_' prong.
var found_underscore = false; var case_has_underscore = false;
for (case.ast.values) |val| { for (case.ast.values) |val| {
switch (tree.nodeTag(val)) { switch (tree.nodeTag(val)) {
.identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {
const case_src = case.ast.arrow_token - 1; const val_src = tree.nodeMainToken(val);
if (underscore_src) |src| { if (underscore_src) |src| {
return astgen.failTokNotes( return astgen.failTokNotes(
case_src, val_src,
"multiple '_' prongs in switch expression", "multiple '_' prongs in switch expression",
.{}, .{},
&[_]u32{ &[_]u32{
@ -7747,39 +7730,26 @@ fn switchExpr(
), ),
}, },
); );
} 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) { if (case.inline_token != null) {
return astgen.failTok(case_src, "cannot inline '_' prong", .{}); return astgen.failTok(val_src, "cannot inline '_' prong", .{});
} }
special_node = case_node.toOptional(); underscore_case_node = case_node.toOptional();
special_prong = if (case.ast.values.len == 1) .under else .absorbing_under; underscore_src = val_src;
underscore_src = case_src;
underscore_node = val.toOptional(); underscore_node = val.toOptional();
found_underscore = true; 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", .{}), .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
else => {}, else => {},
} }
} }
if (found_underscore) continue; if (case_has_underscore) continue;
if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
scalar_cases_len += 1; scalar_cases_len += 1;
@ -7791,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 }; const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none };
astgen.advanceSourceCursorToNode(operand_node); astgen.advanceSourceCursorToNode(operand_node);
@ -7811,7 +7789,9 @@ fn switchExpr(
const payloads = &astgen.scratch; const payloads = &astgen.scratch;
const scratch_top = astgen.scratch.items.len; const scratch_top = astgen.scratch.items.len;
const case_table_start = scratch_top; 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 multi_case_table = scalar_case_table + scalar_cases_len;
const case_table_end = multi_case_table + multi_cases_len; const case_table_end = multi_case_table + multi_cases_len;
try astgen.scratch.resize(gpa, case_table_end); try astgen.scratch.resize(gpa, case_table_end);
@ -7943,9 +7923,19 @@ fn switchExpr(
const header_index: u32 = @intCast(payloads.items.len); const header_index: u32 = @intCast(payloads.items.len);
const body_len_index = if (is_multi_case) blk: { const body_len_index = if (is_multi_case) blk: {
if (case_node.toOptional() == special_node) { if (case_node.toOptional() == underscore_case_node) {
assert(special_prong == .absorbing_under); payloads.items[under_case_index] = header_index;
payloads.items[case_table_start] = 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 { } else {
payloads.items[multi_case_table + multi_case_index] = header_index; payloads.items[multi_case_table + multi_case_index] = header_index;
multi_case_index += 1; multi_case_index += 1;
@ -7985,9 +7975,13 @@ fn switchExpr(
payloads.items[header_index] = items_len; payloads.items[header_index] = items_len;
payloads.items[header_index + 1] = ranges_len; payloads.items[header_index + 1] = ranges_len;
break :blk header_index + 2; break :blk header_index + 2;
} else if (case_node.toOptional() == special_node) blk: { } else if (case_node.toOptional() == else_case_node) blk: {
assert(special_prong != .absorbing_under); payloads.items[else_case_index] = header_index;
payloads.items[case_table_start] = 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 try payloads.resize(gpa, header_index + 1); // body_len
break :blk header_index; break :blk header_index;
} else blk: { } else blk: {
@ -8048,7 +8042,7 @@ fn switchExpr(
.operand = raw_operand, .operand = raw_operand,
.bits = Zir.Inst.SwitchBlock.Bits{ .bits = Zir.Inst.SwitchBlock.Bits{
.has_multi_cases = multi_cases_len != 0, .has_multi_cases = multi_cases_len != 0,
.special_prong = special_prong, .special_prongs = special_prongs,
.any_has_tag_capture = any_has_tag_capture, .any_has_tag_capture = any_has_tag_capture,
.any_non_inline_capture = any_non_inline_capture, .any_non_inline_capture = any_non_inline_capture,
.has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
@ -8067,29 +8061,40 @@ fn switchExpr(
const zir_datas = astgen.instructions.items(.data); const zir_datas = astgen.instructions.items(.data);
zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
var normal_case_table_start = case_table_start; if (has_else) {
if (special_prong != .none) { const start_index = payloads.items[else_case_index];
normal_case_table_start += 1; var end_index = start_index + 1;
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]);
const start_index = payloads.items[case_table_start]; 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 body_len_index = start_index;
var end_index = start_index; var end_index = start_index;
if (special_prong == .absorbing_under) { switch (underscore_additional_items) {
.none => {
end_index += 1;
},
.one => {
body_len_index += 1;
end_index += 2;
},
.many => {
body_len_index += 2; body_len_index += 2;
const items_len = payloads.items[start_index]; const items_len = payloads.items[start_index];
const ranges_len = payloads.items[start_index + 1]; const ranges_len = payloads.items[start_index + 1];
end_index += 3 + items_len + 2 * ranges_len; end_index += 3 + items_len + 2 * ranges_len;
} else { },
end_index += 1;
} }
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
end_index += prong_info.body_len; end_index += prong_info.body_len;
astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
} }
for (payloads.items[normal_case_table_start..case_table_end], 0..) |start_index, i| { for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| {
var body_len_index = start_index; var body_len_index = start_index;
var end_index = start_index; var end_index = start_index;
const table_index = normal_case_table_start + i; const table_index = scalar_case_table + i;
if (table_index < multi_case_table) { if (table_index < multi_case_table) {
body_len_index += 1; body_len_index += 1;
end_index += 2; end_index += 2;

View File

@ -3226,9 +3226,14 @@ pub const Inst = struct {
/// 0. multi_cases_len: u32 // If has_multi_cases is set. /// 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. /// 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 special_prong != .none /// 2. else_body { // If special_prong.hasElse() is set.
/// items_len: u32, // If special_prong == .absorbing_under /// info: ProngInfo,
/// ranges_len: u32, // If special_prong == .absorbing_under /// body member Index for every info.body_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, /// info: ProngInfo,
/// item: Ref, // for every items_len /// item: Ref, // for every items_len
/// ranges: { // for every ranges_len /// ranges: { // for every ranges_len
@ -3237,12 +3242,12 @@ pub const Inst = struct {
/// } /// }
/// body member Index for every info.body_len /// body member Index for every info.body_len
/// } /// }
/// 3. scalar_cases: { // for every scalar_cases_len /// 4. scalar_cases: { // for every scalar_cases_len
/// item: Ref, /// item: Ref,
/// info: ProngInfo, /// info: ProngInfo,
/// body member Index for every info.body_len /// 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, /// items_len: u32,
/// ranges_len: u32, /// ranges_len: u32,
/// info: ProngInfo, /// info: ProngInfo,
@ -3283,16 +3288,17 @@ pub const Inst = struct {
/// If true, one or more prongs have multiple items. /// If true, one or more prongs have multiple items.
has_multi_cases: bool, has_multi_cases: bool,
/// Information about the special prong. /// Information about the special prong.
special_prong: SpecialProng, special_prongs: SpecialProngs,
/// If true, at least one prong has an inline tag capture. /// If true, at least one prong has an inline tag capture.
any_has_tag_capture: bool, any_has_tag_capture: bool,
/// If true, at least one prong has a capture which may not /// If true, at least one prong has a capture which may not
/// be comptime-known via `inline`. /// be comptime-known via `inline`.
any_non_inline_capture: bool, any_non_inline_capture: bool,
/// If true, at least one prong contains a `continue`.
has_continue: bool, has_continue: bool,
scalar_cases_len: ScalarCasesLen, scalar_cases_len: ScalarCasesLen,
pub const ScalarCasesLen = u26; pub const ScalarCasesLen = u25;
}; };
pub const MultiProng = struct { pub const MultiProng = struct {
@ -3868,17 +3874,67 @@ pub const Inst = struct {
}; };
}; };
pub const SpecialProng = enum(u2) { pub const SpecialProngs = enum(u3) {
none, none = 0b000,
/// Simple else prong. /// Simple `else` prong.
/// `else => {}` /// `else => {},`
@"else", @"else" = 0b001,
/// Simple '_' prong. /// Simple `_` prong.
/// `_ => {}` /// `_ => {},`
under, under = 0b010,
/// '_' prong with additional items. /// Both an `else` and a `_` prong.
/// `a, _, b => {}` /// `else => {},`
absorbing_under, /// `_ => {},`
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 { pub const DeclIterator = struct {
@ -4723,7 +4779,7 @@ fn findTrackableSwitch(
} }
const has_special = switch (kind) { const has_special = switch (kind) {
.normal => extra.data.bits.special_prong != .none, .normal => extra.data.bits.special_prongs != .none,
.err_union => has_special: { .err_union => has_special: {
// Handle `non_err_body` first. // Handle `non_err_body` first.
const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
@ -4738,23 +4794,11 @@ fn findTrackableSwitch(
}; };
if (has_special) { if (has_special) {
if (kind == .normal) { const has_else = if (kind == .normal)
if (extra.data.bits.special_prong == .absorbing_under) { extra.data.bits.special_prongs.hasElse()
const items_len = zir.extra[extra_index]; else
extra_index += 1; true;
const ranges_len = zir.extra[extra_index]; if (has_else) {
extra_index += 1;
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
extra_index += 1;
extra_index += items_len + ranges_len * 2;
const body = zir.bodySlice(extra_index, prong_info.body_len);
extra_index += body.len;
try zir.findTrackableBody(gpa, contents, defers, body);
}
}
const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
extra_index += 1; extra_index += 1;
const body = zir.bodySlice(extra_index, prong_info.body_len); const body = zir.bodySlice(extra_index, prong_info.body_len);
@ -4762,6 +4806,29 @@ fn findTrackableSwitch(
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);
}
}
}
{ {
const scalar_cases_len = extra.data.bits.scalar_cases_len; const scalar_cases_len = extra.data.bits.scalar_cases_len;

View File

@ -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 = block.nodeOffset(inst_data.src_node);
const switch_src_node_offset = 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 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 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_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 }); 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, err_val,
operand_err_set_ty, operand_err_set_ty,
switch_src_node_offset, switch_src_node_offset,
null,
.{ .{
.body = else_case.body, .body = else_case.body,
.end = else_case.end, .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, .is_inline = else_case.is_inline,
.has_tag_capture = false, .has_tag_capture = false,
}, },
false,
case_vals, case_vals,
scalar_cases_len, scalar_cases_len,
multi_cases_len, multi_cases_len,
@ -11200,6 +11202,7 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
true, true,
switch_src_node_offset, switch_src_node_offset,
else_prong_src, else_prong_src,
false,
undefined, undefined,
seen_errors, seen_errors,
undefined, undefined,
@ -11207,6 +11210,10 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
undefined, undefined,
cond_dbg_node_index, cond_dbg_node_index,
true, true,
null,
undefined,
&.{},
&.{},
); );
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len + 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 pt = sema.pt;
const zcu = pt.zcu; const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const gpa = sema.gpa; const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node); const src = block.nodeOffset(inst_data.src_node);
const src_node_offset = 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 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 extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: { const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {
@ -11335,19 +11344,14 @@ 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); var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
defer case_vals.deinit(gpa); defer case_vals.deinit(gpa);
var single_absorbed_item: Zir.Inst.Ref = .none;
var absorbed_items: []const Zir.Inst.Ref = &.{}; var absorbed_items: []const Zir.Inst.Ref = &.{};
var absorbed_ranges: []const Zir.Inst.Ref = &.{}; var absorbed_ranges: []const Zir.Inst.Ref = &.{};
const special_prong = extra.data.bits.special_prong; const special_prongs = extra.data.bits.special_prongs;
const special: SpecialProng = switch (special_prong) { const has_else = special_prongs.hasElse();
.none => .{ const has_under = special_prongs.hasUnder();
.body = &.{}, const special_else: SpecialProng = if (has_else) blk: {
.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 info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);
const extra_body_start = header_extra_index + 1; const extra_body_start = header_extra_index + 1;
break :blk .{ break :blk .{
@ -11357,19 +11361,31 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
.is_inline = info.is_inline, .is_inline = info.is_inline,
.has_tag_capture = info.has_tag_capture, .has_tag_capture = info.has_tag_capture,
}; };
}, } else .{
.absorbing_under => blk: { .body = &.{},
var extra_index = header_extra_index; .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]; const items_len = sema.code.extra[extra_index];
extra_index += 1; extra_index += 1;
const ranges_len = sema.code.extra[extra_index]; const ranges_len = sema.code.extra[extra_index];
extra_index += 1; 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]); const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1; extra_index += 1 + trailing_items_len;
absorbed_items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len;
absorbed_ranges = sema.code.refSlice(extra_index, ranges_len * 2);
extra_index += ranges_len * 2;
break :blk .{ break :blk .{
.body = sema.code.bodySlice(extra_index, info.body_len), .body = sema.code.bodySlice(extra_index, info.body_len),
.end = extra_index + info.body_len, .end = extra_index + info.body_len,
@ -11377,8 +11393,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
.is_inline = info.is_inline, .is_inline = info.is_inline,
.has_tag_capture = info.has_tag_capture, .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`. // Duplicate checking variables later also used for `inline else`.
var seen_enum_fields: []?LazySrcLoc = &.{}; var seen_enum_fields: []?LazySrcLoc = &.{};
@ -11398,9 +11420,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
var else_error_ty: ?Type = null; var else_error_ty: ?Type = null;
// Validate usage of '_' prongs. // Validate usage of '_' prongs.
if ((special_prong == .under or special_prong == .absorbing_under) and if (has_under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {
!raw_operand_ty.isNonexhaustiveEnum(zcu))
{
const msg = msg: { const msg = msg: {
const msg = try sema.errMsg( const msg = try sema.errMsg(
src, src,
@ -11409,7 +11429,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
); );
errdefer msg.destroy(gpa); errdefer msg.destroy(gpa);
try sema.errNote( try sema.errNote(
special_prong_src, under_prong_src,
msg, msg,
"'_' prong here", "'_' prong here",
.{}, .{},
@ -11443,14 +11463,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
cond_ty, cond_ty,
block.src(.{ .switch_case_item = .{ block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset, .switch_node_offset = src_node_offset,
.case_idx = .special, .case_idx = .special_under,
.item_idx = .{ .kind = .single, .index = @intCast(item_i) }, .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }), } }),
); );
} }
try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset); try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset);
var extra_index: usize = special.end; var extra_index: usize = special_end;
{ {
var scalar_i: u32 = 0; var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -11508,13 +11528,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
if (seen_src == null) break false; if (seen_src == null) break false;
} else true; } else true;
if (special_prong == .@"else") { if (has_else) {
if (all_tags_handled and !cond_ty.isNonexhaustiveEnum(zcu)) return sema.fail( if (all_tags_handled) {
if (cond_ty.isNonexhaustiveEnum(zcu)) {
if (has_under) return sema.fail(
block, block,
special_prong_src, 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", "unreachable else prong; all cases already handled",
.{}, .{},
); );
}
} else if (!all_tags_handled) { } else if (!all_tags_handled) {
const msg = msg: { const msg = msg: {
const msg = try sema.errMsg( const msg = try sema.errMsg(
@ -11532,7 +11561,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
i, i,
msg, msg,
"unhandled enumeration value: '{f}'", "unhandled enumeration value: '{f}'",
.{field_name.fmt(&zcu.intern_pool)}, .{field_name.fmt(ip)},
); );
} }
try sema.errNote( try sema.errNote(
@ -11544,11 +11573,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
break :msg msg; break :msg msg;
}; };
return sema.failWithOwnedErrorMsg(block, 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( return sema.fail(
block, block,
src, src,
"switch on non-exhaustive enum must include 'else' or '_' prong", "switch on non-exhaustive enum must include 'else' or '_' prong or both",
.{}, .{},
); );
} }
@ -11562,11 +11591,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
inst_data, inst_data,
scalar_cases_len, scalar_cases_len,
multi_cases_len, multi_cases_len,
.{ .body = special.body, .end = special.end, .src = special_prong_src }, .{ .body = special_else.body, .end = special_else.end, .src = else_prong_src },
special_prong == .@"else", has_else,
), ),
.int, .comptime_int => { .int, .comptime_int => {
var extra_index: usize = special.end; var extra_index: usize = special_end;
{ {
var scalar_i: u32 = 0; var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -11648,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 min_int = try cond_ty.minInt(pt, cond_ty);
const max_int = try cond_ty.maxInt(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 (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {
if (special_prong == .@"else") { if (has_else) {
return sema.fail( return sema.fail(
block, block,
special_prong_src, else_prong_src,
"unreachable else prong; all cases already handled", "unreachable else prong; all cases already handled",
.{}, .{},
); );
@ -11659,7 +11688,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
break :check_range; break :check_range;
} }
} }
if (special_prong != .@"else") { if (special_prongs == .none) {
return sema.fail( return sema.fail(
block, block,
src, src,
@ -11670,7 +11699,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
} }
}, },
.bool => { .bool => {
var extra_index: usize = special.end; var extra_index: usize = special_end;
{ {
var scalar_i: u32 = 0; var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -11722,18 +11751,16 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset); try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
} }
} }
switch (special_prong) { if (has_else) {
.@"else" => {
if (true_count + false_count == 2) { if (true_count + false_count == 2) {
return sema.fail( return sema.fail(
block, block,
special_prong_src, else_prong_src,
"unreachable else prong; all cases already handled", "unreachable else prong; all cases already handled",
.{}, .{},
); );
} }
}, } else {
.under, .absorbing_under, .none => {
if (true_count + false_count < 2) { if (true_count + false_count < 2) {
return sema.fail( return sema.fail(
block, block,
@ -11742,11 +11769,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
.{}, .{},
); );
} }
},
} }
}, },
.enum_literal, .void, .@"fn", .pointer, .type => { .enum_literal, .void, .@"fn", .pointer, .type => {
if (special_prong != .@"else") { if (!has_else) {
return sema.fail( return sema.fail(
block, block,
src, src,
@ -11758,7 +11784,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
var seen_values = ValueSrcMap{}; var seen_values = ValueSrcMap{};
defer seen_values.deinit(gpa); defer seen_values.deinit(gpa);
var extra_index: usize = special.end; var extra_index: usize = special_end;
{ {
var scalar_i: u32 = 0; var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -11831,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 = .{ const spa: SwitchProngAnalysis = .{
.sema = sema, .sema = sema,
.parent_block = block, .parent_block = block,
@ -11877,11 +11913,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
defer child_block.instructions.deinit(gpa); defer child_block.instructions.deinit(gpa);
defer merges.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) { if (empty_enum) {
return .void_value; return .void_value;
} }
if (special_prong == .none) { if (special_prongs == .none) {
return sema.fail(block, src, "switch must handle all possibilities", .{}); return sema.fail(block, src, "switch must handle all possibilities", .{});
} }
const init_cond = switch (operand) { const init_cond = switch (operand) {
@ -11895,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); const ok = try block.addUnOp(.is_named_enum_value, init_cond);
try sema.addSafetyCheck(block, src, ok, .corrupt_switch); 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; return .unreachable_value;
} }
} }
@ -11915,7 +11954,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
cond_ty, cond_ty,
cond_val, cond_val,
src_node_offset, src_node_offset,
special, special_members_only,
special_generic,
has_under,
case_vals, case_vals,
scalar_cases_len, scalar_cases_len,
multi_cases_len, multi_cases_len,
@ -11925,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( return spa.resolveProngComptime(
&child_block, &child_block,
.special, .special,
special.body, special_generic.body,
special.capture, special_generic.capture,
block.src(.{ .switch_capture = .{ block.src(.{ .switch_capture = .{
.switch_node_offset = src_node_offset, .switch_node_offset = src_node_offset,
.case_idx = .special, .case_idx = if (has_under) .special_under else .special_else,
} }), } }),
undefined, // case_vals may be undefined for special prongs undefined, // case_vals may be undefined for special prongs
.none, .none,
@ -11949,6 +11994,88 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
unreachable; 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) |special| gen: {
assert(cond_ty.isNonexhaustiveEnum(zcu));
_ = special;
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( const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
spa, spa,
&child_block, &child_block,
@ -11960,14 +12087,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
cond_ty, cond_ty,
operand_src, operand_src,
case_vals, case_vals,
special, special_generic,
scalar_cases_len, scalar_cases_len,
multi_cases_len, multi_cases_len,
union_originally, union_originally,
raw_operand_ty, raw_operand_ty,
err_set, err_set,
src_node_offset, src_node_offset,
special_prong_src, special_generic_src,
has_under,
seen_enum_fields, seen_enum_fields,
seen_errors, seen_errors,
range_set, range_set,
@ -11975,6 +12103,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
false_count, false_count,
cond_dbg_node_index, cond_dbg_node_index,
false, 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| { for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
@ -12058,14 +12190,15 @@ fn analyzeSwitchRuntimeBlock(
operand_ty: Type, operand_ty: Type,
operand_src: LazySrcLoc, operand_src: LazySrcLoc,
case_vals: std.ArrayListUnmanaged(Air.Inst.Ref), case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
special: SpecialProng, else_prong: SpecialProng,
scalar_cases_len: usize, scalar_cases_len: usize,
multi_cases_len: usize, multi_cases_len: usize,
union_originally: bool, union_originally: bool,
maybe_union_ty: Type, maybe_union_ty: Type,
err_set: bool, err_set: bool,
switch_node_offset: std.zig.Ast.Node.Offset, 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_enum_fields: []?LazySrcLoc,
seen_errors: SwitchErrorSet, seen_errors: SwitchErrorSet,
range_set: RangeSet, range_set: RangeSet,
@ -12073,6 +12206,11 @@ fn analyzeSwitchRuntimeBlock(
false_count: u8, false_count: u8,
cond_dbg_node_index: Zir.Inst.Index, cond_dbg_node_index: Zir.Inst.Index,
allow_err_code_unwrap: bool, 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 { ) CompileError!Air.Inst.Ref {
const pt = sema.pt; const pt = sema.pt;
const zcu = pt.zcu; const zcu = pt.zcu;
@ -12096,7 +12234,7 @@ fn analyzeSwitchRuntimeBlock(
case_block.need_debug_scope = null; // this body is emitted regardless case_block.need_debug_scope = null; // this body is emitted regardless
defer case_block.instructions.deinit(gpa); defer case_block.instructions.deinit(gpa);
var extra_index: usize = special.end; var extra_index: usize = else_prong.end;
var scalar_i: usize = 0; var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -12158,23 +12296,42 @@ fn analyzeSwitchRuntimeBlock(
var cases_len = scalar_cases_len; var cases_len = scalar_cases_len;
var case_val_idx: usize = 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; var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) { 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]; const items_len = sema.code.extra[extra_index];
extra_index += 1; extra_index += 1;
const ranges_len = sema.code.extra[extra_index]; const ranges_len = sema.code.extra[extra_index];
extra_index += 1; extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]); info = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + items_len + 2 * ranges_len; extra_index += 1 + items_len + ranges_len * 2;
const items = case_vals.items[case_val_idx..][0..items_len]; items = case_vals.items[case_val_idx..][0..items_len];
case_val_idx += items_len; case_val_idx += items_len;
// TODO: @ptrCast slice once Sema supports it ranges = @ptrCast(case_vals.items[case_val_idx..][0 .. ranges_len * 2]);
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; case_val_idx += ranges_len * 2;
const body = sema.code.bodySlice(extra_index, info.body_len); body = sema.code.bodySlice(extra_index, info.body_len);
extra_index += info.body_len; extra_index += info.body_len;
}
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; case_block.error_return_trace_index = child_block.error_return_trace_index;
@ -12184,14 +12341,29 @@ fn analyzeSwitchRuntimeBlock(
var emit_bb = false; var emit_bb = false;
for (ranges, 0..) |range_items, range_i| { for (ranges, 0..) |range_items, range_i| {
var item = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable; var item = sema.resolveConstDefinedValue(block, .unneeded, range_items[0], undefined) catch unreachable;
const item_last = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, range_items[1], 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)) : ({ while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
// Previous validation has resolved any possible lazy values. // 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); 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; cases_len += 1;
@ -12200,11 +12372,14 @@ fn analyzeSwitchRuntimeBlock(
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) try sema.emitBackwardBranch(block, block.src(.{ .switch_case_item = .{ 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, .switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .range, .index = @intCast(range_i) }, .item_idx = .{ .kind = .range, .index = @intCast(range_i) },
} })); } });
try sema.emitBackwardBranch(block, bb_src);
}
emit_bb = true; emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime( const prong_hint = try spa.analyzeProngRuntime(
@ -12249,11 +12424,14 @@ fn analyzeSwitchRuntimeBlock(
break :blk field_ty.zigTypeTag(zcu) != .noreturn; break :blk field_ty.zigTypeTag(zcu) != .noreturn;
} else true; } else true;
if (emit_bb) try sema.emitBackwardBranch(block, block.src(.{ .switch_case_item = .{ 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, .switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) }, .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) }, .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} })); } });
try sema.emitBackwardBranch(block, bb_src);
}
emit_bb = true; emit_bb = true;
const prong_hint: std.builtin.BranchHint = if (analyze_body) h: { const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
@ -12329,11 +12507,11 @@ fn analyzeSwitchRuntimeBlock(
try branch_hints.append(gpa, prong_hint); try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + 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); case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = @intCast(items.len), .items_len = @intCast(items.len),
.ranges_len = @intCast(ranges_len), .ranges_len = @intCast(ranges.len),
.body_len = @intCast(case_block.instructions.items.len), .body_len = @intCast(case_block.instructions.items.len),
})); }));
@ -12350,12 +12528,14 @@ fn analyzeSwitchRuntimeBlock(
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); 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; 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" => { .@"enum" => {
if (operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) { 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), operand_ty.fmt(pt),
}); });
} }
@ -12374,22 +12554,22 @@ fn analyzeSwitchRuntimeBlock(
break :blk field_ty.zigTypeTag(zcu) != .noreturn; break :blk field_ty.zigTypeTag(zcu) != .noreturn;
} else true; } 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; emit_bb = true;
const prong_hint: std.builtin.BranchHint = if (analyze_body) h: { const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
break :h try spa.analyzeProngRuntime( break :h try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
&.{item_ref}, &.{item_ref},
item_ref, item_ref,
special.has_tag_capture, else_prong.has_tag_capture,
); );
} else h: { } else h: {
_ = try case_block.addNoOp(.unreach); _ = try case_block.addNoOp(.unreach);
@ -12411,7 +12591,7 @@ fn analyzeSwitchRuntimeBlock(
}, },
.error_set => { .error_set => {
if (operand_ty.isAnyError(zcu)) { 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), operand_ty.fmt(pt),
}); });
} }
@ -12430,21 +12610,21 @@ fn analyzeSwitchRuntimeBlock(
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; 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; emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime( const prong_hint = try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
&.{item_ref}, &.{item_ref},
item_ref, item_ref,
special.has_tag_capture, else_prong.has_tag_capture,
); );
try branch_hints.append(gpa, prong_hint); try branch_hints.append(gpa, prong_hint);
@ -12470,21 +12650,21 @@ fn analyzeSwitchRuntimeBlock(
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; 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; emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime( const prong_hint = try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
&.{item_ref}, &.{item_ref},
item_ref, item_ref,
special.has_tag_capture, else_prong.has_tag_capture,
); );
try branch_hints.append(gpa, prong_hint); try branch_hints.append(gpa, prong_hint);
@ -12507,21 +12687,21 @@ fn analyzeSwitchRuntimeBlock(
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; 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; emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime( const prong_hint = try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
&.{.bool_true}, &.{.bool_true},
.bool_true, .bool_true,
special.has_tag_capture, else_prong.has_tag_capture,
); );
try branch_hints.append(gpa, prong_hint); try branch_hints.append(gpa, prong_hint);
@ -12542,21 +12722,21 @@ fn analyzeSwitchRuntimeBlock(
case_block.instructions.shrinkRetainingCapacity(0); case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index; 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; emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime( const prong_hint = try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
&.{.bool_false}, &.{.bool_false},
.bool_false, .bool_false,
special.has_tag_capture, else_prong.has_tag_capture,
); );
try branch_hints.append(gpa, prong_hint); try branch_hints.append(gpa, prong_hint);
@ -12572,7 +12752,7 @@ fn analyzeSwitchRuntimeBlock(
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); 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), operand_ty.fmt(pt),
}), }),
}; };
@ -12581,7 +12761,7 @@ fn analyzeSwitchRuntimeBlock(
case_block.error_return_trace_index = child_block.error_return_trace_index; case_block.error_return_trace_index = child_block.error_return_trace_index;
if (zcu.backendSupportsFeature(.is_named_enum_value) and 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.zigTypeTag(zcu) == .@"enum" and
(!operand_ty.isNonexhaustiveEnum(zcu) or union_originally)) (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
{ {
@ -12590,7 +12770,12 @@ fn analyzeSwitchRuntimeBlock(
try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch); 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| { for (seen_enum_fields, 0..) |seen_field, index| {
if (seen_field != null) continue; if (seen_field != null) continue;
const union_obj = zcu.typeToUnion(maybe_union_ty).?; const union_obj = zcu.typeToUnion(maybe_union_ty).?;
@ -12599,20 +12784,20 @@ fn analyzeSwitchRuntimeBlock(
} else false } else false
else else
true; true;
const else_hint: std.builtin.BranchHint = if (special.body.len != 0 and err_set and const else_hint: std.builtin.BranchHint = if (else_prong.body.len != 0 and err_set and
try sema.maybeErrorUnwrap(&case_block, special.body, operand, operand_src, allow_err_code_unwrap)) try sema.maybeErrorUnwrap(&case_block, else_prong.body, operand, operand_src, allow_err_code_unwrap))
h: { h: {
// nothing to do here. weight against error branch // nothing to do here. weight against error branch
break :h .unlikely; 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( break :h try spa.analyzeProngRuntime(
&case_block, &case_block,
.special, .special,
special.body, else_prong.body,
special.capture, else_prong.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = else_src_idx,
} }), } }),
undefined, // case_vals may be undefined for special prongs undefined, // case_vals may be undefined for special prongs
.none, .none,
@ -12686,7 +12871,9 @@ fn resolveSwitchComptimeLoop(
cond_ty: Type, cond_ty: Type,
init_cond_val: Value, init_cond_val: Value,
switch_node_offset: std.zig.Ast.Node.Offset, 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), case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
scalar_cases_len: u32, scalar_cases_len: u32,
multi_cases_len: u32, multi_cases_len: u32,
@ -12706,7 +12893,9 @@ fn resolveSwitchComptimeLoop(
cond_val, cond_val,
cond_ty, cond_ty,
switch_node_offset, switch_node_offset,
special, special_members_only,
special_generic,
special_generic_is_under,
case_vals, case_vals,
scalar_cases_len, scalar_cases_len,
multi_cases_len, multi_cases_len,
@ -12754,17 +12943,20 @@ fn resolveSwitchComptime(
operand_val: Value, operand_val: Value,
operand_ty: Type, operand_ty: Type,
switch_node_offset: std.zig.Ast.Node.Offset, 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), case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
scalar_cases_len: u32, scalar_cases_len: u32,
multi_cases_len: u32, multi_cases_len: u32,
err_set: bool, err_set: bool,
empty_enum: bool, empty_enum: bool,
) CompileError!Air.Inst.Ref { ) CompileError!Air.Inst.Ref {
const zcu = sema.pt.zcu;
const merges = &child_block.label.?.merges; const merges = &child_block.label.?.merges;
const resolved_operand_val = try sema.resolveLazyValue(operand_val); 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; var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
@ -12865,11 +13057,13 @@ fn resolveSwitchComptime(
extra_index += info.body_len; 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) { if (empty_enum) {
return .void_value; return .void_value;
} }
if (special_members_only) |special| {
assert(operand_ty.isNonexhaustiveEnum(zcu));
if (operand_ty.enumTagFieldIndex(operand_val, zcu)) |_| {
return spa.resolveProngComptime( return spa.resolveProngComptime(
child_block, child_block,
.special, .special,
@ -12877,13 +13071,33 @@ fn resolveSwitchComptime(
special.capture, special.capture,
child_block.src(.{ .switch_capture = .{ child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset, .switch_node_offset = switch_node_offset,
.case_idx = .special, .case_idx = .special_else,
} }), } }),
undefined, // case_vals may be undefined for special prongs undefined, // case_vals may be undefined for special prongs
if (special.is_inline) cond_operand else .none, if (special.is_inline) cond_operand else .none,
special.has_tag_capture, special.has_tag_capture,
merges, merges,
); );
}
}
return spa.resolveProngComptime(
child_block,
.special,
special_generic.body,
special_generic.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = if (special_generic_is_under)
.special_under
else
.special_else,
} }),
undefined, // case_vals may be undefined for special prongs
if (special_generic.is_inline) cond_operand else .none,
special_generic.has_tag_capture,
merges,
);
} }
const RangeSetUnhandledIterator = struct { const RangeSetUnhandledIterator = struct {

View File

@ -1677,20 +1677,37 @@ pub const SrcLoc = struct {
return tree.nodeToSpan(condition); 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 tree = try src_loc.file_scope.getTree(zcu);
const switch_node = node_off.toAbsolute(src_loc.base_node); const switch_node = node_off.toAbsolute(src_loc.base_node);
_, const extra_index = tree.nodeData(switch_node).node_and_extra; _, 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); const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
for (case_nodes) |case_node| { for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?; const case = tree.fullSwitchCase(case_node).?;
if (case.isSpecial(tree)) |special_node| { if (case.ast.values.len == 0) {
return tree.nodeToSpan(case_node);
}
} 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( return tree.tokensToSpan(
tree.firstToken(case_node), tree.firstToken(case_node),
tree.lastToken(case_node), tree.lastToken(case_node),
tree.nodeMainToken(special_node.unwrap() orelse case_node), tree.nodeMainToken(val),
); );
} }
}
} else unreachable; } else unreachable;
}, },
@ -1701,10 +1718,6 @@ pub const SrcLoc = struct {
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index); const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
for (case_nodes) |case_node| { for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?; const case = tree.fullSwitchCase(case_node).?;
if (case.isSpecial(tree)) |maybe_else| {
if (maybe_else == .none) continue;
}
for (case.ast.values) |item_node| { for (case.ast.values) |item_node| {
if (tree.nodeTag(item_node) == .switch_range) { if (tree.nodeTag(item_node) == .switch_range) {
return tree.nodeToSpan(item_node); return tree.nodeToSpan(item_node);
@ -2109,32 +2122,35 @@ pub const SrcLoc = struct {
var multi_i: u32 = 0; var multi_i: u32 = 0;
var scalar_i: u32 = 0; var scalar_i: u32 = 0;
var found_special = false;
var underscore_node: Ast.Node.OptionalIndex = .none; var underscore_node: Ast.Node.OptionalIndex = .none;
const case = for (case_nodes) |case_node| { const case = case: for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?; const case = tree.fullSwitchCase(case_node).?;
const is_special = special: { if (case.ast.values.len == 0) {
if (found_special) break :special false; if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_else) {
if (case.isSpecial(tree)) |special_node| { break :case case;
underscore_node = special_node; }
found_special = true; continue :case;
break :special true; }
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;
} }
break :special false;
}; };
if (is_special) {
if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special) {
break case;
}
continue;
}
const is_multi = case.ast.values.len != 1 or const is_multi = case.ast.values.len != 1 or
tree.nodeTag(case.ast.values[0]) == .switch_range; tree.nodeTag(case.ast.values[0]) == .switch_range;
switch (want_case_idx.kind) { switch (want_case_idx.kind) {
.scalar => if (!is_multi and want_case_idx.index == scalar_i) break case, .scalar => if (!is_multi and want_case_idx.index == scalar_i)
.multi => if (is_multi and want_case_idx.index == multi_i) break case, break :case case,
.multi => if (is_multi and want_case_idx.index == multi_i)
break :case case,
} }
if (is_multi) { if (is_multi) {
@ -2148,7 +2164,10 @@ pub const SrcLoc = struct {
.switch_case_item, .switch_case_item,
.switch_case_item_range_first, .switch_case_item_range_first,
.switch_case_item_range_last, .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 => { .switch_capture, .switch_tag_capture => {
const start = switch (src_loc.lazy) { const start = switch (src_loc.lazy) {
.switch_capture => case.payload_token.?, .switch_capture => case.payload_token.?,
@ -2369,10 +2388,14 @@ pub const LazySrcLoc = struct {
/// by taking this AST node index offset from the containing base node, /// 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. /// which points to a switch expression AST node. Next, navigate to the operand.
node_offset_switch_operand: Ast.Node.Offset, 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, /// 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. /// which points to a switch expression AST node. Next, navigate to the else prong.
node_offset_switch_special_prong: Ast.Node.Offset, 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 /// 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, /// 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 /// which points to a switch expression AST node. Next, navigate to any of the
@ -2568,7 +2591,8 @@ pub const LazySrcLoc = struct {
kind: enum(u1) { scalar, multi }, kind: enum(u1) { scalar, multi },
index: u31, index: u31,
pub const special: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32))); 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) { pub const SwitchItemIndex = packed struct(u32) {

View File

@ -2087,19 +2087,40 @@ const Writer = struct {
self.indent += 2; self.indent += 2;
else_prong: { const special_prongs = extra.data.bits.special_prongs;
const special_prong = extra.data.bits.special_prong;
if (special_prong == .none) break :else_prong;
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 ",
.by_ref => "by_ref ",
};
const inline_text = if (info.is_inline) "inline " else "";
extra_index += 1;
const body = self.code.bodySlice(extra_index, info.body_len);
extra_index += body.len;
try stream.writeAll(",\n");
try stream.splatByteAll(' ', self.indent);
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 items_len: u32 = 0;
var ranges_len: u32 = 0; var ranges_len: u32 = 0;
if (special_prong == .absorbing_under) { 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]; items_len = self.code.extra[extra_index];
extra_index += 1; extra_index += 1;
ranges_len = self.code.extra[extra_index]; ranges_len = self.code.extra[extra_index];
extra_index += 1; 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; extra_index += 1;
const items = self.code.refSlice(extra_index, items_len); const items = self.code.refSlice(extra_index, items_len);
extra_index += items_len; extra_index += items_len;
@ -2112,12 +2133,12 @@ const Writer = struct {
.by_ref => try stream.writeAll("by_ref "), .by_ref => try stream.writeAll("by_ref "),
} }
if (info.is_inline) try stream.writeAll("inline "); if (info.is_inline) try stream.writeAll("inline ");
switch (special_prong) {
.@"else" => try stream.writeAll("else"),
.under, .absorbing_under => try stream.writeAll("_"),
.none => unreachable,
}
try stream.writeAll("_");
if (single_item_ref != .none) {
try stream.writeAll(", ");
try self.writeInstRef(stream, single_item_ref);
}
for (items) |item_ref| { for (items) |item_ref| {
try stream.writeAll(", "); try stream.writeAll(", ");
try self.writeInstRef(stream, item_ref); try self.writeInstRef(stream, item_ref);
@ -2125,9 +2146,9 @@ const Writer = struct {
var range_i: usize = 0; var range_i: usize = 0;
while (range_i < ranges_len) : (range_i += 1) { 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; 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; extra_index += 1;
try stream.writeAll(", "); try stream.writeAll(", ");
@ -2146,9 +2167,9 @@ const Writer = struct {
const scalar_cases_len = extra.data.bits.scalar_cases_len; const scalar_cases_len = extra.data.bits.scalar_cases_len;
var scalar_i: usize = 0; var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) { 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; 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; extra_index += 1;
const body = self.code.bodySlice(extra_index, info.body_len); const body = self.code.bodySlice(extra_index, info.body_len);
extra_index += info.body_len; extra_index += info.body_len;
@ -2173,7 +2194,7 @@ const Writer = struct {
extra_index += 1; extra_index += 1;
const ranges_len = self.code.extra[extra_index]; const ranges_len = self.code.extra[extra_index];
extra_index += 1; 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; extra_index += 1;
const items = self.code.refSlice(extra_index, items_len); const items = self.code.refSlice(extra_index, items_len);
extra_index += items_len; extra_index += items_len;
@ -2194,9 +2215,9 @@ const Writer = struct {
var range_i: usize = 0; var range_i: usize = 0;
while (range_i < ranges_len) : (range_i += 1) { 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; 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; extra_index += 1;
if (range_i != 0 or items.len != 0) { if (range_i != 0 or items.len != 0) {

View File

@ -1075,15 +1075,15 @@ test "switch on 8-bit mod result" {
} }
test "switch on non-exhaustive enum" { test "switch on non-exhaustive enum" {
const E = enum(u32) { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO
const E = enum(u4) {
a, a,
b, b,
c, c,
_, _,
};
var e: E = .a; fn doTheTest(e: @This()) !void {
_ = &e;
switch (e) { switch (e) {
.a, .b => {}, .a, .b => {},
else => return error.TestFailed, else => return error.TestFailed,
@ -1097,4 +1097,28 @@ test "switch on non-exhaustive enum" {
.a, .b => {}, .a, .b => {},
.c, _ => return error.TestFailed, .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,
}
}
};
var e: E = .a;
_ = &e;
try E.doTheTest(e);
try comptime E.doTheTest(.a);
} }

View File

@ -249,3 +249,27 @@ test "switch loop on larger than pointer integer" {
} }
try expect(entry == 3); 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();
}

View File

@ -0,0 +1,27 @@
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
// backend=stage2
// target=native
//
// :10:16: error: cannot inline '_' prong
// :18:16: error: cannot inline '_' prong

View File

@ -0,0 +1,18 @@
const E = enum(u8) {
a,
b,
_,
};
export fn f(e: E) void {
switch (e) {
.a, .b, _ => {},
else => {},
}
}
// error
// backend=stage2
// target=native
//
// :10:14: error: unreachable else prong; all explicit cases already handled

View File

@ -37,7 +37,7 @@ pub export fn entry3() void {
// :12:5: error: switch must handle all possibilities // :12:5: error: switch must handle all possibilities
// :3:5: note: unhandled enumeration value: 'b' // :3:5: note: unhandled enumeration value: 'b'
// :1:11: note: enum 'tmp.E' declared here // :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 // :26:5: error: '_' prong only allowed when switching on non-exhaustive enums
// :29:9: note: '_' prong here // :29:9: note: '_' prong here
// :26:5: note: consider using 'else' // :26:5: note: consider using 'else'