Merge pull request #24381 from Justus2308/switch-better-underscore
Enhance switch on non-exhaustive enums
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user