Skip to content

Commit 77df0b9

Browse files
committed
Permit explicit tags with '_' switch prong
Mainly affects ZIR representation of switch_block[_ref] and special prong (detection) logic for switch. Adds a new SpecialProng tag 'absorbing_under' that allows specifying additional explicit tags in a '_' prong which are respected when checking that every value is handled during semantic analysis but are not transformed into AIR and instead 'absorbed' by the '_' branch.
1 parent fc2c188 commit 77df0b9

File tree

10 files changed

+333
-127
lines changed

10 files changed

+333
-127
lines changed

lib/std/zig/Ast.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2824,6 +2824,24 @@ pub const full = struct {
28242824
arrow_token: TokenIndex,
28252825
target_expr: Node.Index,
28262826
};
2827+
2828+
/// Returns:
2829+
/// `null` if case is not special
2830+
/// `.none` if case is else prong
2831+
/// Index of underscore otherwise
2832+
pub fn isSpecial(case: *const SwitchCase, tree: *const Ast) ?Node.OptionalIndex {
2833+
if (case.ast.values.len == 0) {
2834+
return .none;
2835+
}
2836+
for (case.ast.values) |val| {
2837+
if (tree.nodeTag(val) == .identifier and
2838+
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
2839+
{
2840+
return val.toOptional();
2841+
}
2842+
}
2843+
return null;
2844+
}
28272845
};
28282846

28292847
pub const Asm = struct {

lib/std/zig/AstGen.zig

Lines changed: 91 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7656,6 +7656,7 @@ fn switchExpr(
76567656
var special_node: Ast.Node.OptionalIndex = .none;
76577657
var else_src: ?Ast.TokenIndex = null;
76587658
var underscore_src: ?Ast.TokenIndex = null;
7659+
var underscore_node: Ast.Node.OptionalIndex = .none;
76597660
for (case_nodes) |case_node| {
76607661
const case = tree.fullSwitchCase(case_node).?;
76617662
if (case.payload_token) |payload_token| {
@@ -7676,7 +7677,7 @@ fn switchExpr(
76767677
any_non_inline_capture = true;
76777678
}
76787679
}
7679-
// Check for else/`_` prong.
7680+
// Check for else prong.
76807681
if (case.ast.values.len == 0) {
76817682
const case_src = case.ast.arrow_token - 1;
76827683
if (else_src) |src| {
@@ -7715,56 +7716,60 @@ fn switchExpr(
77157716
special_prong = .@"else";
77167717
else_src = case_src;
77177718
continue;
7718-
} else if (case.ast.values.len == 1 and
7719-
tree.nodeTag(case.ast.values[0]) == .identifier and
7720-
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"))
7721-
{
7722-
const case_src = case.ast.arrow_token - 1;
7723-
if (underscore_src) |src| {
7724-
return astgen.failTokNotes(
7725-
case_src,
7726-
"multiple '_' prongs in switch expression",
7727-
.{},
7728-
&[_]u32{
7729-
try astgen.errNoteTok(
7730-
src,
7731-
"previous '_' prong here",
7732-
.{},
7733-
),
7734-
},
7735-
);
7736-
} else if (else_src) |some_else| {
7737-
return astgen.failNodeNotes(
7738-
node,
7739-
"else and '_' prong in switch expression",
7740-
.{},
7741-
&[_]u32{
7742-
try astgen.errNoteTok(
7743-
some_else,
7744-
"else prong here",
7745-
.{},
7746-
),
7747-
try astgen.errNoteTok(
7748-
case_src,
7749-
"'_' prong here",
7750-
.{},
7751-
),
7752-
},
7753-
);
7754-
}
7755-
if (case.inline_token != null) {
7756-
return astgen.failTok(case_src, "cannot inline '_' prong", .{});
7757-
}
7758-
special_node = case_node.toOptional();
7759-
special_prong = .under;
7760-
underscore_src = case_src;
7761-
continue;
77627719
}
77637720

7721+
// Check for '_' prong.
7722+
var found_underscore = false;
77647723
for (case.ast.values) |val| {
7765-
if (tree.nodeTag(val) == .string_literal)
7766-
return astgen.failNode(val, "cannot switch on strings", .{});
7724+
switch (tree.nodeTag(val)) {
7725+
.identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {
7726+
const case_src = case.ast.arrow_token - 1;
7727+
if (underscore_src) |src| {
7728+
return astgen.failTokNotes(
7729+
case_src,
7730+
"multiple '_' prongs in switch expression",
7731+
.{},
7732+
&[_]u32{
7733+
try astgen.errNoteTok(
7734+
src,
7735+
"previous '_' prong here",
7736+
.{},
7737+
),
7738+
},
7739+
);
7740+
} else if (else_src) |some_else| {
7741+
return astgen.failNodeNotes(
7742+
node,
7743+
"else and '_' prong in switch expression",
7744+
.{},
7745+
&[_]u32{
7746+
try astgen.errNoteTok(
7747+
some_else,
7748+
"else prong here",
7749+
.{},
7750+
),
7751+
try astgen.errNoteTok(
7752+
case_src,
7753+
"'_' prong here",
7754+
.{},
7755+
),
7756+
},
7757+
);
7758+
}
7759+
if (case.inline_token != null) {
7760+
return astgen.failTok(case_src, "cannot inline '_' prong", .{});
7761+
}
7762+
special_node = case_node.toOptional();
7763+
special_prong = if (case.ast.values.len == 1) .under else .absorbing_under;
7764+
underscore_src = case_src;
7765+
underscore_node = val.toOptional();
7766+
found_underscore = true;
7767+
},
7768+
.string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
7769+
else => {},
7770+
}
77677771
}
7772+
if (found_underscore) continue;
77687773

77697774
if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
77707775
scalar_cases_len += 1;
@@ -7928,14 +7933,23 @@ fn switchExpr(
79287933

79297934
const header_index: u32 = @intCast(payloads.items.len);
79307935
const body_len_index = if (is_multi_case) blk: {
7931-
payloads.items[multi_case_table + multi_case_index] = header_index;
7932-
multi_case_index += 1;
7936+
if (case_node.toOptional() == special_node) {
7937+
assert(special_prong == .absorbing_under);
7938+
payloads.items[case_table_start] = header_index;
7939+
} else {
7940+
payloads.items[multi_case_table + multi_case_index] = header_index;
7941+
multi_case_index += 1;
7942+
}
79337943
try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
79347944

79357945
// items
79367946
var items_len: u32 = 0;
79377947
for (case.ast.values) |item_node| {
7938-
if (tree.nodeTag(item_node) == .switch_range) continue;
7948+
if (item_node.toOptional() == underscore_node or
7949+
tree.nodeTag(item_node) == .switch_range)
7950+
{
7951+
continue;
7952+
}
79397953
items_len += 1;
79407954

79417955
const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
@@ -7945,7 +7959,9 @@ fn switchExpr(
79457959
// ranges
79467960
var ranges_len: u32 = 0;
79477961
for (case.ast.values) |range| {
7948-
if (tree.nodeTag(range) != .switch_range) continue;
7962+
if (tree.nodeTag(range) != .switch_range) {
7963+
continue;
7964+
}
79497965
ranges_len += 1;
79507966

79517967
const first_node, const last_node = tree.nodeData(range).node_and_node;
@@ -7960,6 +7976,7 @@ fn switchExpr(
79607976
payloads.items[header_index + 1] = ranges_len;
79617977
break :blk header_index + 2;
79627978
} else if (case_node.toOptional() == special_node) blk: {
7979+
assert(special_prong != .absorbing_under);
79637980
payloads.items[case_table_start] = header_index;
79647981
try payloads.resize(gpa, header_index + 1); // body_len
79657982
break :blk header_index;
@@ -8015,15 +8032,13 @@ fn switchExpr(
80158032
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +
80168033
@intFromBool(multi_cases_len != 0) +
80178034
@intFromBool(any_has_tag_capture) +
8018-
payloads.items.len - case_table_end +
8019-
(case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len);
8035+
payloads.items.len - scratch_top);
80208036

80218037
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
80228038
.operand = raw_operand,
80238039
.bits = Zir.Inst.SwitchBlock.Bits{
80248040
.has_multi_cases = multi_cases_len != 0,
8025-
.has_else = special_prong == .@"else",
8026-
.has_under = special_prong == .under,
8041+
.special_prong = special_prong,
80278042
.any_has_tag_capture = any_has_tag_capture,
80288043
.any_non_inline_capture = any_non_inline_capture,
80298044
.has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
@@ -8042,13 +8057,30 @@ fn switchExpr(
80428057
const zir_datas = astgen.instructions.items(.data);
80438058
zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
80448059

8045-
for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {
8060+
var normal_case_table_start = case_table_start;
8061+
if (special_prong != .none) {
8062+
normal_case_table_start += 1;
8063+
8064+
const start_index = payloads.items[case_table_start];
80468065
var body_len_index = start_index;
80478066
var end_index = start_index;
8048-
const table_index = case_table_start + i;
8049-
if (table_index < scalar_case_table) {
8067+
if (special_prong == .absorbing_under) {
8068+
body_len_index += 2;
8069+
const items_len = payloads.items[start_index];
8070+
const ranges_len = payloads.items[start_index + 1];
8071+
end_index += 3 + items_len + 2 * ranges_len;
8072+
} else {
80508073
end_index += 1;
8051-
} else if (table_index < multi_case_table) {
8074+
}
8075+
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
8076+
end_index += prong_info.body_len;
8077+
astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
8078+
}
8079+
for (payloads.items[normal_case_table_start..case_table_end], 0..) |start_index, i| {
8080+
var body_len_index = start_index;
8081+
var end_index = start_index;
8082+
const table_index = normal_case_table_start + i;
8083+
if (table_index < multi_case_table) {
80528084
body_len_index += 1;
80538085
end_index += 2;
80548086
} else {

lib/std/zig/Zir.zig

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3225,8 +3225,15 @@ pub const Inst = struct {
32253225

32263226
/// 0. multi_cases_len: u32 // If has_multi_cases is set.
32273227
/// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
3228-
/// 2. else_body { // If has_else or has_under is set.
3228+
/// 2. else_body { // If special_prong != .none
3229+
/// items_len: u32, // If special_prong == .absorbing_under
3230+
/// ranges_len: u32, // If special_prong == .absorbing_under
32293231
/// info: ProngInfo,
3232+
/// item: Ref, // for every items_len
3233+
/// ranges: { // for every ranges_len
3234+
/// item_first: Ref,
3235+
/// item_last: Ref,
3236+
/// }
32303237
/// body member Index for every info.body_len
32313238
/// }
32323239
/// 3. scalar_cases: { // for every scalar_cases_len
@@ -3238,7 +3245,7 @@ pub const Inst = struct {
32383245
/// items_len: u32,
32393246
/// ranges_len: u32,
32403247
/// info: ProngInfo,
3241-
/// item: Ref // for every items_len
3248+
/// item: Ref, // for every items_len
32423249
/// ranges: { // for every ranges_len
32433250
/// item_first: Ref,
32443251
/// item_last: Ref,
@@ -3274,10 +3281,8 @@ pub const Inst = struct {
32743281
pub const Bits = packed struct(u32) {
32753282
/// If true, one or more prongs have multiple items.
32763283
has_multi_cases: bool,
3277-
/// If true, there is an else prong. This is mutually exclusive with `has_under`.
3278-
has_else: bool,
3279-
/// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
3280-
has_under: bool,
3284+
/// Information about the special prong.
3285+
special_prong: SpecialProng,
32813286
/// If true, at least one prong has an inline tag capture.
32823287
any_has_tag_capture: bool,
32833288
/// If true, at least one prong has a capture which may not
@@ -3287,17 +3292,6 @@ pub const Inst = struct {
32873292
scalar_cases_len: ScalarCasesLen,
32883293

32893294
pub const ScalarCasesLen = u26;
3290-
3291-
pub fn specialProng(bits: Bits) SpecialProng {
3292-
const has_else: u2 = @intFromBool(bits.has_else);
3293-
const has_under: u2 = @intFromBool(bits.has_under);
3294-
return switch ((has_else << 1) | has_under) {
3295-
0b00 => .none,
3296-
0b01 => .under,
3297-
0b10 => .@"else",
3298-
0b11 => unreachable,
3299-
};
3300-
}
33013295
};
33023296

33033297
pub const MultiProng = struct {
@@ -3872,7 +3866,18 @@ pub const Inst = struct {
38723866
};
38733867
};
38743868

3875-
pub const SpecialProng = enum { none, @"else", under };
3869+
pub const SpecialProng = enum(u2) {
3870+
none,
3871+
/// Simple else prong.
3872+
/// `else => {}`
3873+
@"else",
3874+
/// Simple '_' prong.
3875+
/// `_ => {}`
3876+
under,
3877+
/// '_' prong with additional items.
3878+
/// `a, _, b => {}`
3879+
absorbing_under,
3880+
};
38763881

38773882
pub const DeclIterator = struct {
38783883
extra_index: u32,
@@ -4716,7 +4721,7 @@ fn findTrackableSwitch(
47164721
}
47174722

47184723
const has_special = switch (kind) {
4719-
.normal => extra.data.bits.specialProng() != .none,
4724+
.normal => extra.data.bits.special_prong != .none,
47204725
.err_union => has_special: {
47214726
// Handle `non_err_body` first.
47224727
const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
@@ -4731,6 +4736,23 @@ fn findTrackableSwitch(
47314736
};
47324737

47334738
if (has_special) {
4739+
if (kind == .normal) {
4740+
if (extra.data.bits.special_prong == .absorbing_under) {
4741+
const items_len = zir.extra[extra_index];
4742+
extra_index += 1;
4743+
const ranges_len = zir.extra[extra_index];
4744+
extra_index += 1;
4745+
const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4746+
extra_index += 1;
4747+
4748+
extra_index += items_len + ranges_len * 2;
4749+
4750+
const body = zir.bodySlice(extra_index, prong_info.body_len);
4751+
extra_index += body.len;
4752+
4753+
try zir.findTrackableBody(gpa, contents, defers, body);
4754+
}
4755+
}
47344756
const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
47354757
extra_index += 1;
47364758
const body = zir.bodySlice(extra_index, prong_info.body_len);

0 commit comments

Comments
 (0)