authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2025-07-08 01:32:49+02:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2025-08-07 13:57:57+02:00
log1d9b1c021273037efea6623e669800ad538c8075
tree95efb3febc06d875e14cb1df105d686b678ff09f
parentfd9cfc39f53fae7fad4eadf9f1d8943f7e2c03b1

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.

10 files changed, 333 insertions(+), 127 deletions(-)

lib/std/zig/Ast.zig+18
...@@ -2877,6 +2877,24 @@ pub const full = struct {...@@ -2877,6 +2877,24 @@ pub const full = struct {
2877 arrow_token: TokenIndex,2877 arrow_token: TokenIndex,
2878 target_expr: Node.Index,2878 target_expr: Node.Index,
2879 };2879 };
2880
2881 /// Returns:
2882 /// `null` if case is not special
2883 /// `.none` if case is else prong
2884 /// Index of underscore otherwise
2885 pub fn isSpecial(case: *const SwitchCase, tree: *const Ast) ?Node.OptionalIndex {
2886 if (case.ast.values.len == 0) {
2887 return .none;
2888 }
2889 for (case.ast.values) |val| {
2890 if (tree.nodeTag(val) == .identifier and
2891 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
2892 {
2893 return val.toOptional();
2894 }
2895 }
2896 return null;
2897 }
2880 };2898 };
28812899
2882 pub const Asm = struct {2900 pub const Asm = struct {
lib/std/zig/AstGen.zig+91-59
...@@ -7666,6 +7666,7 @@ fn switchExpr(...@@ -7666,6 +7666,7 @@ fn switchExpr(
7666 var special_node: Ast.Node.OptionalIndex = .none;7666 var special_node: Ast.Node.OptionalIndex = .none;
7667 var else_src: ?Ast.TokenIndex = null;7667 var else_src: ?Ast.TokenIndex = null;
7668 var underscore_src: ?Ast.TokenIndex = null;7668 var underscore_src: ?Ast.TokenIndex = null;
7669 var underscore_node: Ast.Node.OptionalIndex = .none;
7669 for (case_nodes) |case_node| {7670 for (case_nodes) |case_node| {
7670 const case = tree.fullSwitchCase(case_node).?;7671 const case = tree.fullSwitchCase(case_node).?;
7671 if (case.payload_token) |payload_token| {7672 if (case.payload_token) |payload_token| {
...@@ -7686,7 +7687,7 @@ fn switchExpr(...@@ -7686,7 +7687,7 @@ fn switchExpr(
7686 any_non_inline_capture = true;7687 any_non_inline_capture = true;
7687 }7688 }
7688 }7689 }
7689 // Check for else/`_` prong.7690 // Check for else prong.
7690 if (case.ast.values.len == 0) {7691 if (case.ast.values.len == 0) {
7691 const case_src = case.ast.arrow_token - 1;7692 const case_src = case.ast.arrow_token - 1;
7692 if (else_src) |src| {7693 if (else_src) |src| {
...@@ -7725,56 +7726,60 @@ fn switchExpr(...@@ -7725,56 +7726,60 @@ fn switchExpr(
7725 special_prong = .@"else";7726 special_prong = .@"else";
7726 else_src = case_src;7727 else_src = case_src;
7727 continue;7728 continue;
7728 } else if (case.ast.values.len == 1 and
7729 tree.nodeTag(case.ast.values[0]) == .identifier and
7730 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"))
7731 {
7732 const case_src = case.ast.arrow_token - 1;
7733 if (underscore_src) |src| {
7734 return astgen.failTokNotes(
7735 case_src,
7736 "multiple '_' prongs in switch expression",
7737 .{},
7738 &[_]u32{
7739 try astgen.errNoteTok(
7740 src,
7741 "previous '_' prong here",
7742 .{},
7743 ),
7744 },
7745 );
7746 } else if (else_src) |some_else| {
7747 return astgen.failNodeNotes(
7748 node,
7749 "else and '_' prong in switch expression",
7750 .{},
7751 &[_]u32{
7752 try astgen.errNoteTok(
7753 some_else,
7754 "else prong here",
7755 .{},
7756 ),
7757 try astgen.errNoteTok(
7758 case_src,
7759 "'_' prong here",
7760 .{},
7761 ),
7762 },
7763 );
7764 }
7765 if (case.inline_token != null) {
7766 return astgen.failTok(case_src, "cannot inline '_' prong", .{});
7767 }
7768 special_node = case_node.toOptional();
7769 special_prong = .under;
7770 underscore_src = case_src;
7771 continue;
7772 }7729 }
77737730
7731 // Check for '_' prong.
7732 var found_underscore = false;
7774 for (case.ast.values) |val| {7733 for (case.ast.values) |val| {
7775 if (tree.nodeTag(val) == .string_literal)7734 switch (tree.nodeTag(val)) {
7776 return astgen.failNode(val, "cannot switch on strings", .{});7735 .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {
7736 const case_src = case.ast.arrow_token - 1;
7737 if (underscore_src) |src| {
7738 return astgen.failTokNotes(
7739 case_src,
7740 "multiple '_' prongs in switch expression",
7741 .{},
7742 &[_]u32{
7743 try astgen.errNoteTok(
7744 src,
7745 "previous '_' prong here",
7746 .{},
7747 ),
7748 },
7749 );
7750 } else if (else_src) |some_else| {
7751 return astgen.failNodeNotes(
7752 node,
7753 "else and '_' prong in switch expression",
7754 .{},
7755 &[_]u32{
7756 try astgen.errNoteTok(
7757 some_else,
7758 "else prong here",
7759 .{},
7760 ),
7761 try astgen.errNoteTok(
7762 case_src,
7763 "'_' prong here",
7764 .{},
7765 ),
7766 },
7767 );
7768 }
7769 if (case.inline_token != null) {
7770 return astgen.failTok(case_src, "cannot inline '_' prong", .{});
7771 }
7772 special_node = case_node.toOptional();
7773 special_prong = if (case.ast.values.len == 1) .under else .absorbing_under;
7774 underscore_src = case_src;
7775 underscore_node = val.toOptional();
7776 found_underscore = true;
7777 },
7778 .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
7779 else => {},
7780 }
7777 }7781 }
7782 if (found_underscore) continue;
77787783
7779 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {7784 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
7780 scalar_cases_len += 1;7785 scalar_cases_len += 1;
...@@ -7938,14 +7943,23 @@ fn switchExpr(...@@ -7938,14 +7943,23 @@ fn switchExpr(
79387943
7939 const header_index: u32 = @intCast(payloads.items.len);7944 const header_index: u32 = @intCast(payloads.items.len);
7940 const body_len_index = if (is_multi_case) blk: {7945 const body_len_index = if (is_multi_case) blk: {
7941 payloads.items[multi_case_table + multi_case_index] = header_index;7946 if (case_node.toOptional() == special_node) {
7942 multi_case_index += 1;7947 assert(special_prong == .absorbing_under);
7948 payloads.items[case_table_start] = header_index;
7949 } else {
7950 payloads.items[multi_case_table + multi_case_index] = header_index;
7951 multi_case_index += 1;
7952 }
7943 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len7953 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
79447954
7945 // items7955 // items
7946 var items_len: u32 = 0;7956 var items_len: u32 = 0;
7947 for (case.ast.values) |item_node| {7957 for (case.ast.values) |item_node| {
7948 if (tree.nodeTag(item_node) == .switch_range) continue;7958 if (item_node.toOptional() == underscore_node or
7959 tree.nodeTag(item_node) == .switch_range)
7960 {
7961 continue;
7962 }
7949 items_len += 1;7963 items_len += 1;
79507964
7951 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);7965 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
...@@ -7955,7 +7969,9 @@ fn switchExpr(...@@ -7955,7 +7969,9 @@ fn switchExpr(
7955 // ranges7969 // ranges
7956 var ranges_len: u32 = 0;7970 var ranges_len: u32 = 0;
7957 for (case.ast.values) |range| {7971 for (case.ast.values) |range| {
7958 if (tree.nodeTag(range) != .switch_range) continue;7972 if (tree.nodeTag(range) != .switch_range) {
7973 continue;
7974 }
7959 ranges_len += 1;7975 ranges_len += 1;
79607976
7961 const first_node, const last_node = tree.nodeData(range).node_and_node;7977 const first_node, const last_node = tree.nodeData(range).node_and_node;
...@@ -7970,6 +7986,7 @@ fn switchExpr(...@@ -7970,6 +7986,7 @@ fn switchExpr(
7970 payloads.items[header_index + 1] = ranges_len;7986 payloads.items[header_index + 1] = ranges_len;
7971 break :blk header_index + 2;7987 break :blk header_index + 2;
7972 } else if (case_node.toOptional() == special_node) blk: {7988 } else if (case_node.toOptional() == special_node) blk: {
7989 assert(special_prong != .absorbing_under);
7973 payloads.items[case_table_start] = header_index;7990 payloads.items[case_table_start] = header_index;
7974 try payloads.resize(gpa, header_index + 1); // body_len7991 try payloads.resize(gpa, header_index + 1); // body_len
7975 break :blk header_index;7992 break :blk header_index;
...@@ -8025,15 +8042,13 @@ fn switchExpr(...@@ -8025,15 +8042,13 @@ fn switchExpr(
8025 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +8042 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +
8026 @intFromBool(multi_cases_len != 0) +8043 @intFromBool(multi_cases_len != 0) +
8027 @intFromBool(any_has_tag_capture) +8044 @intFromBool(any_has_tag_capture) +
8028 payloads.items.len - case_table_end +8045 payloads.items.len - scratch_top);
8029 (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len);
80308046
8031 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{8047 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
8032 .operand = raw_operand,8048 .operand = raw_operand,
8033 .bits = Zir.Inst.SwitchBlock.Bits{8049 .bits = Zir.Inst.SwitchBlock.Bits{
8034 .has_multi_cases = multi_cases_len != 0,8050 .has_multi_cases = multi_cases_len != 0,
8035 .has_else = special_prong == .@"else",8051 .special_prong = special_prong,
8036 .has_under = special_prong == .under,
8037 .any_has_tag_capture = any_has_tag_capture,8052 .any_has_tag_capture = any_has_tag_capture,
8038 .any_non_inline_capture = any_non_inline_capture,8053 .any_non_inline_capture = any_non_inline_capture,
8039 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,8054 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
...@@ -8052,13 +8067,30 @@ fn switchExpr(...@@ -8052,13 +8067,30 @@ fn switchExpr(
8052 const zir_datas = astgen.instructions.items(.data);8067 const zir_datas = astgen.instructions.items(.data);
8053 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;8068 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
80548069
8055 for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {8070 var normal_case_table_start = case_table_start;
8071 if (special_prong != .none) {
8072 normal_case_table_start += 1;
8073
8074 const start_index = payloads.items[case_table_start];
8056 var body_len_index = start_index;8075 var body_len_index = start_index;
8057 var end_index = start_index;8076 var end_index = start_index;
8058 const table_index = case_table_start + i;8077 if (special_prong == .absorbing_under) {
8059 if (table_index < scalar_case_table) {8078 body_len_index += 2;
8079 const items_len = payloads.items[start_index];
8080 const ranges_len = payloads.items[start_index + 1];
8081 end_index += 3 + items_len + 2 * ranges_len;
8082 } else {
8060 end_index += 1;8083 end_index += 1;
8061 } else if (table_index < multi_case_table) {8084 }
8085 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
8086 end_index += prong_info.body_len;
8087 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
8088 }
8089 for (payloads.items[normal_case_table_start..case_table_end], 0..) |start_index, i| {
8090 var body_len_index = start_index;
8091 var end_index = start_index;
8092 const table_index = normal_case_table_start + i;
8093 if (table_index < multi_case_table) {
8062 body_len_index += 1;8094 body_len_index += 1;
8063 end_index += 2;8095 end_index += 2;
8064 } else {8096 } else {
lib/std/zig/Zir.zig+41-19
...@@ -3226,8 +3226,15 @@ pub const Inst = struct {...@@ -3226,8 +3226,15 @@ pub const Inst = struct {
32263226
3227 /// 0. multi_cases_len: u32 // If has_multi_cases is set.3227 /// 0. multi_cases_len: u32 // If has_multi_cases is set.
3228 /// 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 /// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
3229 /// 2. else_body { // If has_else or has_under is set.3229 /// 2. else_body { // If special_prong != .none
3230 /// items_len: u32, // If special_prong == .absorbing_under
3231 /// ranges_len: u32, // If special_prong == .absorbing_under
3230 /// info: ProngInfo,3232 /// info: ProngInfo,
3233 /// item: Ref, // for every items_len
3234 /// ranges: { // for every ranges_len
3235 /// item_first: Ref,
3236 /// item_last: Ref,
3237 /// }
3231 /// body member Index for every info.body_len3238 /// body member Index for every info.body_len
3232 /// }3239 /// }
3233 /// 3. scalar_cases: { // for every scalar_cases_len3240 /// 3. scalar_cases: { // for every scalar_cases_len
...@@ -3239,7 +3246,7 @@ pub const Inst = struct {...@@ -3239,7 +3246,7 @@ pub const Inst = struct {
3239 /// items_len: u32,3246 /// items_len: u32,
3240 /// ranges_len: u32,3247 /// ranges_len: u32,
3241 /// info: ProngInfo,3248 /// info: ProngInfo,
3242 /// item: Ref // for every items_len3249 /// item: Ref, // for every items_len
3243 /// ranges: { // for every ranges_len3250 /// ranges: { // for every ranges_len
3244 /// item_first: Ref,3251 /// item_first: Ref,
3245 /// item_last: Ref,3252 /// item_last: Ref,
...@@ -3275,10 +3282,8 @@ pub const Inst = struct {...@@ -3275,10 +3282,8 @@ pub const Inst = struct {
3275 pub const Bits = packed struct(u32) {3282 pub const Bits = packed struct(u32) {
3276 /// If true, one or more prongs have multiple items.3283 /// If true, one or more prongs have multiple items.
3277 has_multi_cases: bool,3284 has_multi_cases: bool,
3278 /// If true, there is an else prong. This is mutually exclusive with `has_under`.3285 /// Information about the special prong.
3279 has_else: bool,3286 special_prong: SpecialProng,
3280 /// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
3281 has_under: bool,
3282 /// If true, at least one prong has an inline tag capture.3287 /// If true, at least one prong has an inline tag capture.
3283 any_has_tag_capture: bool,3288 any_has_tag_capture: bool,
3284 /// If true, at least one prong has a capture which may not3289 /// If true, at least one prong has a capture which may not
...@@ -3288,17 +3293,6 @@ pub const Inst = struct {...@@ -3288,17 +3293,6 @@ pub const Inst = struct {
3288 scalar_cases_len: ScalarCasesLen,3293 scalar_cases_len: ScalarCasesLen,
32893294
3290 pub const ScalarCasesLen = u26;3295 pub const ScalarCasesLen = u26;
3291
3292 pub fn specialProng(bits: Bits) SpecialProng {
3293 const has_else: u2 = @intFromBool(bits.has_else);
3294 const has_under: u2 = @intFromBool(bits.has_under);
3295 return switch ((has_else << 1) | has_under) {
3296 0b00 => .none,
3297 0b01 => .under,
3298 0b10 => .@"else",
3299 0b11 => unreachable,
3300 };
3301 }
3302 };3296 };
33033297
3304 pub const MultiProng = struct {3298 pub const MultiProng = struct {
...@@ -3874,7 +3868,18 @@ pub const Inst = struct {...@@ -3874,7 +3868,18 @@ pub const Inst = struct {
3874 };3868 };
3875};3869};
38763870
3877pub const SpecialProng = enum { none, @"else", under };3871pub const SpecialProng = enum(u2) {
3872 none,
3873 /// Simple else prong.
3874 /// `else => {}`
3875 @"else",
3876 /// Simple '_' prong.
3877 /// `_ => {}`
3878 under,
3879 /// '_' prong with additional items.
3880 /// `a, _, b => {}`
3881 absorbing_under,
3882};
38783883
3879pub const DeclIterator = struct {3884pub const DeclIterator = struct {
3880 extra_index: u32,3885 extra_index: u32,
...@@ -4718,7 +4723,7 @@ fn findTrackableSwitch(...@@ -4718,7 +4723,7 @@ fn findTrackableSwitch(
4718 }4723 }
47194724
4720 const has_special = switch (kind) {4725 const has_special = switch (kind) {
4721 .normal => extra.data.bits.specialProng() != .none,4726 .normal => extra.data.bits.special_prong != .none,
4722 .err_union => has_special: {4727 .err_union => has_special: {
4723 // Handle `non_err_body` first.4728 // Handle `non_err_body` first.
4724 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);4729 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
...@@ -4733,6 +4738,23 @@ fn findTrackableSwitch(...@@ -4733,6 +4738,23 @@ fn findTrackableSwitch(
4733 };4738 };
47344739
4735 if (has_special) {4740 if (has_special) {
4741 if (kind == .normal) {
4742 if (extra.data.bits.special_prong == .absorbing_under) {
4743 const items_len = zir.extra[extra_index];
4744 extra_index += 1;
4745 const ranges_len = zir.extra[extra_index];
4746 extra_index += 1;
4747 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4748 extra_index += 1;
4749
4750 extra_index += items_len + ranges_len * 2;
4751
4752 const body = zir.bodySlice(extra_index, prong_info.body_len);
4753 extra_index += body.len;
4754
4755 try zir.findTrackableBody(gpa, contents, defers, body);
4756 }
4757 }
4736 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);4758 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4737 extra_index += 1;4759 extra_index += 1;
4738 const body = zir.bodySlice(extra_index, prong_info.body_len);4760 const body = zir.bodySlice(extra_index, prong_info.body_len);
src/Sema.zig+52-11
...@@ -11335,7 +11335,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11335,7 +11335,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11335 var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);11335 var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
11336 defer case_vals.deinit(gpa);11336 defer case_vals.deinit(gpa);
1133711337
11338 const special_prong = extra.data.bits.specialProng();11338 var absorbed_items: []const Zir.Inst.Ref = &.{};
11339 var absorbed_ranges: []const Zir.Inst.Ref = &.{};
11340
11341 const special_prong = extra.data.bits.special_prong;
11339 const special: SpecialProng = switch (special_prong) {11342 const special: SpecialProng = switch (special_prong) {
11340 .none => .{11343 .none => .{
11341 .body = &.{},11344 .body = &.{},
...@@ -11355,6 +11358,26 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11355,6 +11358,26 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11355 .has_tag_capture = info.has_tag_capture,11358 .has_tag_capture = info.has_tag_capture,
11356 };11359 };
11357 },11360 },
11361 .absorbing_under => blk: {
11362 var extra_index = header_extra_index;
11363 const items_len = sema.code.extra[extra_index];
11364 extra_index += 1;
11365 const ranges_len = sema.code.extra[extra_index];
11366 extra_index += 1;
11367 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11368 extra_index += 1;
11369 absorbed_items = sema.code.refSlice(extra_index, items_len);
11370 extra_index += items_len;
11371 absorbed_ranges = sema.code.refSlice(extra_index, ranges_len * 2);
11372 extra_index += ranges_len * 2;
11373 break :blk .{
11374 .body = sema.code.bodySlice(extra_index, info.body_len),
11375 .end = extra_index + info.body_len,
11376 .capture = info.capture,
11377 .is_inline = info.is_inline,
11378 .has_tag_capture = info.has_tag_capture,
11379 };
11380 },
11358 };11381 };
1135911382
11360 // Duplicate checking variables later also used for `inline else`.11383 // Duplicate checking variables later also used for `inline else`.
...@@ -11375,7 +11398,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11375,7 +11398,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11375 var else_error_ty: ?Type = null;11398 var else_error_ty: ?Type = null;
1137611399
11377 // Validate usage of '_' prongs.11400 // Validate usage of '_' prongs.
11378 if (special_prong == .under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {11401 if ((special_prong == .under or special_prong == .absorbing_under) and
11402 !raw_operand_ty.isNonexhaustiveEnum(zcu))
11403 {
11379 const msg = msg: {11404 const msg = msg: {
11380 const msg = try sema.errMsg(11405 const msg = try sema.errMsg(
11381 src,11406 src,
...@@ -11409,6 +11434,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11409,6 +11434,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11409 @memset(seen_enum_fields, null);11434 @memset(seen_enum_fields, null);
11410 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.11435 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
1141111436
11437 for (absorbed_items, 0..) |item_ref, item_i| {
11438 _ = try sema.validateSwitchItemEnum(
11439 block,
11440 seen_enum_fields,
11441 &range_set,
11442 item_ref,
11443 cond_ty,
11444 block.src(.{ .switch_case_item = .{
11445 .switch_node_offset = src_node_offset,
11446 .case_idx = .special,
11447 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11448 } }),
11449 );
11450 }
11451 try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset);
11452
11412 var extra_index: usize = special.end;11453 var extra_index: usize = special.end;
11413 {11454 {
11414 var scalar_i: u32 = 0;11455 var scalar_i: u32 = 0;
...@@ -11692,7 +11733,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11692,7 +11733,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11692 );11733 );
11693 }11734 }
11694 },11735 },
11695 .under, .none => {11736 .under, .absorbing_under, .none => {
11696 if (true_count + false_count < 2) {11737 if (true_count + false_count < 2) {
11697 return sema.fail(11738 return sema.fail(
11698 block,11739 block,
...@@ -11892,7 +11933,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11892,7 +11933,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11892 special.capture,11933 special.capture,
11893 block.src(.{ .switch_capture = .{11934 block.src(.{ .switch_capture = .{
11894 .switch_node_offset = src_node_offset,11935 .switch_node_offset = src_node_offset,
11895 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,11936 .case_idx = .special,
11896 } }),11937 } }),
11897 undefined, // case_vals may be undefined for special prongs11938 undefined, // case_vals may be undefined for special prongs
11898 .none,11939 .none,
...@@ -12344,7 +12385,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12344,7 +12385,7 @@ fn analyzeSwitchRuntimeBlock(
12344 special.capture,12385 special.capture,
12345 child_block.src(.{ .switch_capture = .{12386 child_block.src(.{ .switch_capture = .{
12346 .switch_node_offset = switch_node_offset,12387 .switch_node_offset = switch_node_offset,
12347 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12388 .case_idx = .special,
12348 } }),12389 } }),
12349 &.{item_ref},12390 &.{item_ref},
12350 item_ref,12391 item_ref,
...@@ -12399,7 +12440,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12399,7 +12440,7 @@ fn analyzeSwitchRuntimeBlock(
12399 special.capture,12440 special.capture,
12400 child_block.src(.{ .switch_capture = .{12441 child_block.src(.{ .switch_capture = .{
12401 .switch_node_offset = switch_node_offset,12442 .switch_node_offset = switch_node_offset,
12402 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12443 .case_idx = .special,
12403 } }),12444 } }),
12404 &.{item_ref},12445 &.{item_ref},
12405 item_ref,12446 item_ref,
...@@ -12439,7 +12480,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12439,7 +12480,7 @@ fn analyzeSwitchRuntimeBlock(
12439 special.capture,12480 special.capture,
12440 child_block.src(.{ .switch_capture = .{12481 child_block.src(.{ .switch_capture = .{
12441 .switch_node_offset = switch_node_offset,12482 .switch_node_offset = switch_node_offset,
12442 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12483 .case_idx = .special,
12443 } }),12484 } }),
12444 &.{item_ref},12485 &.{item_ref},
12445 item_ref,12486 item_ref,
...@@ -12476,7 +12517,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12476,7 +12517,7 @@ fn analyzeSwitchRuntimeBlock(
12476 special.capture,12517 special.capture,
12477 child_block.src(.{ .switch_capture = .{12518 child_block.src(.{ .switch_capture = .{
12478 .switch_node_offset = switch_node_offset,12519 .switch_node_offset = switch_node_offset,
12479 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12520 .case_idx = .special,
12480 } }),12521 } }),
12481 &.{.bool_true},12522 &.{.bool_true},
12482 .bool_true,12523 .bool_true,
...@@ -12511,7 +12552,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12511,7 +12552,7 @@ fn analyzeSwitchRuntimeBlock(
12511 special.capture,12552 special.capture,
12512 child_block.src(.{ .switch_capture = .{12553 child_block.src(.{ .switch_capture = .{
12513 .switch_node_offset = switch_node_offset,12554 .switch_node_offset = switch_node_offset,
12514 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12555 .case_idx = .special,
12515 } }),12556 } }),
12516 &.{.bool_false},12557 &.{.bool_false},
12517 .bool_false,12558 .bool_false,
...@@ -12571,7 +12612,7 @@ fn analyzeSwitchRuntimeBlock(...@@ -12571,7 +12612,7 @@ fn analyzeSwitchRuntimeBlock(
12571 special.capture,12612 special.capture,
12572 child_block.src(.{ .switch_capture = .{12613 child_block.src(.{ .switch_capture = .{
12573 .switch_node_offset = switch_node_offset,12614 .switch_node_offset = switch_node_offset,
12574 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12615 .case_idx = .special,
12575 } }),12616 } }),
12576 undefined, // case_vals may be undefined for special prongs12617 undefined, // case_vals may be undefined for special prongs
12577 .none,12618 .none,
...@@ -12836,7 +12877,7 @@ fn resolveSwitchComptime(...@@ -12836,7 +12877,7 @@ fn resolveSwitchComptime(
12836 special.capture,12877 special.capture,
12837 child_block.src(.{ .switch_capture = .{12878 child_block.src(.{ .switch_capture = .{
12838 .switch_node_offset = switch_node_offset,12879 .switch_node_offset = switch_node_offset,
12839 .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special,12880 .case_idx = .special,
12840 } }),12881 } }),
12841 undefined, // case_vals may be undefined for special prongs12882 undefined, // case_vals may be undefined for special prongs
12842 if (special.is_inline) cond_operand else .none,12883 if (special.is_inline) cond_operand else .none,
src/Zcu.zig+26-21
...@@ -1684,13 +1684,13 @@ pub const SrcLoc = struct {...@@ -1684,13 +1684,13 @@ pub const SrcLoc = struct {
1684 const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);1684 const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
1685 for (case_nodes) |case_node| {1685 for (case_nodes) |case_node| {
1686 const case = tree.fullSwitchCase(case_node).?;1686 const case = tree.fullSwitchCase(case_node).?;
1687 const is_special = (case.ast.values.len == 0) or1687 if (case.isSpecial(tree)) |special_node| {
1688 (case.ast.values.len == 1 and1688 return tree.tokensToSpan(
1689 tree.nodeTag(case.ast.values[0]) == .identifier and1689 tree.firstToken(case_node),
1690 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"));1690 tree.lastToken(case_node),
1691 if (!is_special) continue;1691 tree.nodeMainToken(special_node.unwrap() orelse case_node),
16921692 );
1693 return tree.nodeToSpan(case_node);1693 }
1694 } else unreachable;1694 } else unreachable;
1695 },1695 },
16961696
...@@ -1701,11 +1701,9 @@ pub const SrcLoc = struct {...@@ -1701,11 +1701,9 @@ pub const SrcLoc = struct {
1701 const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);1701 const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
1702 for (case_nodes) |case_node| {1702 for (case_nodes) |case_node| {
1703 const case = tree.fullSwitchCase(case_node).?;1703 const case = tree.fullSwitchCase(case_node).?;
1704 const is_special = (case.ast.values.len == 0) or1704 if (case.isSpecial(tree)) |maybe_else| {
1705 (case.ast.values.len == 1 and1705 if (maybe_else == .none) continue;
1706 tree.nodeTag(case.ast.values[0]) == .identifier and1706 }
1707 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"));
1708 if (is_special) continue;
17091707
1710 for (case.ast.values) |item_node| {1708 for (case.ast.values) |item_node| {
1711 if (tree.nodeTag(item_node) == .switch_range) {1709 if (tree.nodeTag(item_node) == .switch_range) {
...@@ -2111,17 +2109,21 @@ pub const SrcLoc = struct {...@@ -2111,17 +2109,21 @@ pub const SrcLoc = struct {
21112109
2112 var multi_i: u32 = 0;2110 var multi_i: u32 = 0;
2113 var scalar_i: u32 = 0;2111 var scalar_i: u32 = 0;
2112 var found_special = false;
2113 var underscore_node: Ast.Node.OptionalIndex = .none;
2114 const case = for (case_nodes) |case_node| {2114 const case = for (case_nodes) |case_node| {
2115 const case = tree.fullSwitchCase(case_node).?;2115 const case = tree.fullSwitchCase(case_node).?;
2116 const is_special = special: {2116 const is_special = special: {
2117 if (case.ast.values.len == 0) break :special true;2117 if (found_special) break :special false;
2118 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .identifier) {2118 if (case.isSpecial(tree)) |special_node| {
2119 break :special mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_");2119 underscore_node = special_node;
2120 found_special = true;
2121 break :special true;
2120 }2122 }
2121 break :special false;2123 break :special false;
2122 };2124 };
2123 if (is_special) {2125 if (is_special) {
2124 if (want_case_idx.isSpecial()) {2126 if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special) {
2125 break case;2127 break case;
2126 }2128 }
2127 continue;2129 continue;
...@@ -2171,7 +2173,11 @@ pub const SrcLoc = struct {...@@ -2171,7 +2173,11 @@ pub const SrcLoc = struct {
2171 .single => {2173 .single => {
2172 var item_i: u32 = 0;2174 var item_i: u32 = 0;
2173 for (case.ast.values) |item_node| {2175 for (case.ast.values) |item_node| {
2174 if (tree.nodeTag(item_node) == .switch_range) continue;2176 if (item_node.toOptional() == underscore_node or
2177 tree.nodeTag(item_node) == .switch_range)
2178 {
2179 continue;
2180 }
2175 if (item_i != want_item.index) {2181 if (item_i != want_item.index) {
2176 item_i += 1;2182 item_i += 1;
2177 continue;2183 continue;
...@@ -2182,7 +2188,9 @@ pub const SrcLoc = struct {...@@ -2182,7 +2188,9 @@ pub const SrcLoc = struct {
2182 .range => {2188 .range => {
2183 var range_i: u32 = 0;2189 var range_i: u32 = 0;
2184 for (case.ast.values) |item_node| {2190 for (case.ast.values) |item_node| {
2185 if (tree.nodeTag(item_node) != .switch_range) continue;2191 if (tree.nodeTag(item_node) != .switch_range) {
2192 continue;
2193 }
2186 if (range_i != want_item.index) {2194 if (range_i != want_item.index) {
2187 range_i += 1;2195 range_i += 1;
2188 continue;2196 continue;
...@@ -2561,9 +2569,6 @@ pub const LazySrcLoc = struct {...@@ -2561,9 +2569,6 @@ pub const LazySrcLoc = struct {
2561 index: u31,2569 index: u31,
25622570
2563 pub const special: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32)));2571 pub const special: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32)));
2564 pub fn isSpecial(idx: SwitchCaseIndex) bool {
2565 return @as(u32, @bitCast(idx)) == @as(u32, @bitCast(special));
2566 }
2567 };2572 };
25682573
2569 pub const SwitchItemIndex = packed struct(u32) {2574 pub const SwitchItemIndex = packed struct(u32) {
src/print_zir.zig+45-15
...@@ -2088,27 +2088,57 @@ const Writer = struct {...@@ -2088,27 +2088,57 @@ const Writer = struct {
2088 self.indent += 2;2088 self.indent += 2;
20892089
2090 else_prong: {2090 else_prong: {
2091 const special_prong = extra.data.bits.specialProng();2091 const special_prong = extra.data.bits.special_prong;
2092 const prong_name = switch (special_prong) {2092 if (special_prong == .none) break :else_prong;
2093 .@"else" => "else",
2094 .under => "_",
2095 else => break :else_prong,
2096 };
20972093
2094 var items_len: u32 = 0;
2095 var ranges_len: u32 = 0;
2096 if (special_prong == .absorbing_under) {
2097 items_len = self.code.extra[extra_index];
2098 extra_index += 1;
2099 ranges_len = self.code.extra[extra_index];
2100 extra_index += 1;
2101 }
2098 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));2102 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2099 const capture_text = switch (info.capture) {
2100 .none => "",
2101 .by_val => "by_val ",
2102 .by_ref => "by_ref ",
2103 };
2104 const inline_text = if (info.is_inline) "inline " else "";
2105 extra_index += 1;2103 extra_index += 1;
2106 const body = self.code.bodySlice(extra_index, info.body_len);2104 const items = self.code.refSlice(extra_index, items_len);
2107 extra_index += body.len;2105 extra_index += items_len;
21082106
2109 try stream.writeAll(",\n");2107 try stream.writeAll(",\n");
2110 try stream.splatByteAll(' ', self.indent);2108 try stream.splatByteAll(' ', self.indent);
2111 try stream.print("{s}{s}{s} => ", .{ capture_text, inline_text, prong_name });2109 switch (info.capture) {
2110 .none => {},
2111 .by_val => try stream.writeAll("by_val "),
2112 .by_ref => try stream.writeAll("by_ref "),
2113 }
2114 if (info.is_inline) try stream.writeAll("inline ");
2115 switch (special_prong) {
2116 .@"else" => try stream.writeAll("else"),
2117 .under, .absorbing_under => try stream.writeAll("_"),
2118 .none => unreachable,
2119 }
2120
2121 for (items) |item_ref| {
2122 try stream.writeAll(", ");
2123 try self.writeInstRef(stream, item_ref);
2124 }
2125
2126 var range_i: usize = 0;
2127 while (range_i < ranges_len) : (range_i += 1) {
2128 const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2129 extra_index += 1;
2130 const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2131 extra_index += 1;
2132
2133 try stream.writeAll(", ");
2134 try self.writeInstRef(stream, item_first);
2135 try stream.writeAll("...");
2136 try self.writeInstRef(stream, item_last);
2137 }
2138
2139 const body = self.code.bodySlice(extra_index, info.body_len);
2140 extra_index += info.body_len;
2141 try stream.writeAll(" => ");
2112 try self.writeBracedBody(stream, body);2142 try self.writeBracedBody(stream, body);
2113 }2143 }
21142144
test/behavior/switch.zig+25
...@@ -1073,3 +1073,28 @@ test "switch on 8-bit mod result" {...@@ -1073,3 +1073,28 @@ test "switch on 8-bit mod result" {
1073 else => unreachable,1073 else => unreachable,
1074 }1074 }
1075}1075}
1076
1077test "switch on non-exhaustive enum" {
1078 const E = enum(u32) {
1079 a,
1080 b,
1081 c,
1082 _,
1083 };
1084
1085 var e: E = .a;
1086 _ = &e;
1087 switch (e) {
1088 .a, .b => {},
1089 else => return error.TestFailed,
1090 }
1091 switch (e) {
1092 .a, .b => {},
1093 .c => return error.TestFailed,
1094 _ => return error.TestFailed,
1095 }
1096 switch (e) {
1097 .a, .b => {},
1098 .c, _ => return error.TestFailed,
1099 }
1100}
test/cases/compile_errors/switch_expression-non_exhaustive_absorbing.zig created+33
...@@ -0,0 +1,33 @@
1const E = enum(u8) {
2 a,
3 b,
4 _,
5};
6const U = union(E) {
7 a: i32,
8 b: u32,
9};
10pub export fn entry1() void {
11 const e: E = .b;
12 switch (e) { // error: switch not handling the tag `b`
13 .a, _ => {},
14 }
15}
16pub export fn entry2() void {
17 const u = U{ .a = 2 };
18 switch (u) { // error: `_` prong not allowed when switching on tagged union
19 .a => {},
20 .b, _ => {},
21 }
22}
23
24// error
25// backend=stage2
26// target=native
27//
28// :12:5: error: switch must handle all possibilities
29// :3:5: note: unhandled enumeration value: 'b'
30// :1:11: note: enum 'tmp.E' declared here
31// :18:5: error: '_' prong only allowed when switching on non-exhaustive enums
32// :20:13: note: '_' prong here
33// :18:5: note: consider using 'else'
test/cases/compile_errors/switching_with_exhaustive_enum_has___prong_.zig+1-1
...@@ -16,5 +16,5 @@ pub export fn entry() void {...@@ -16,5 +16,5 @@ pub export fn entry() void {
16// target=native16// target=native
17//17//
18// :7:5: error: '_' prong only allowed when switching on non-exhaustive enums18// :7:5: error: '_' prong only allowed when switching on non-exhaustive enums
19// :10:11: note: '_' prong here19// :10:9: note: '_' prong here
20// :7:5: note: consider using 'else'20// :7:5: note: consider using 'else'
test/cases/compile_errors/switching_with_non-exhaustive_enums.zig+1-1
...@@ -39,5 +39,5 @@ pub export fn entry3() void {...@@ -39,5 +39,5 @@ pub export fn entry3() void {
39// :1:11: note: enum 'tmp.E' declared here39// :1:11: note: enum 'tmp.E' declared here
40// :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong40// :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong
41// :26:5: error: '_' prong only allowed when switching on non-exhaustive enums41// :26:5: error: '_' prong only allowed when switching on non-exhaustive enums
42// :29:11: note: '_' prong here42// :29:9: note: '_' prong here
43// :26:5: note: consider using 'else'43// :26:5: note: consider using 'else'