| author | |
| committer | |
| log | 1d9b1c021273037efea6623e669800ad538c8075 |
| tree | 95efb3febc06d875e14cb1df105d686b678ff09f |
| parent | fd9cfc39f53fae7fad4eadf9f1d8943f7e2c03b1 |
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 | 2877 | arrow_token: TokenIndex, |
| 2878 | 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 | }; |
| 2881 | 2899 | |
| 2882 | 2900 | pub const Asm = struct { |
lib/std/zig/AstGen.zig+91-59| ... | ... | @@ -7666,6 +7666,7 @@ fn switchExpr( |
| 7666 | 7666 | var special_node: Ast.Node.OptionalIndex = .none; |
| 7667 | 7667 | var else_src: ?Ast.TokenIndex = null; |
| 7668 | 7668 | var underscore_src: ?Ast.TokenIndex = null; |
| 7669 | var underscore_node: Ast.Node.OptionalIndex = .none; | |
| 7669 | 7670 | for (case_nodes) |case_node| { |
| 7670 | 7671 | const case = tree.fullSwitchCase(case_node).?; |
| 7671 | 7672 | if (case.payload_token) |payload_token| { |
| ... | ... | @@ -7686,7 +7687,7 @@ fn switchExpr( |
| 7686 | 7687 | any_non_inline_capture = true; |
| 7687 | 7688 | } |
| 7688 | 7689 | } |
| 7689 | // Check for else/`_` prong. | |
| 7690 | // Check for else prong. | |
| 7690 | 7691 | if (case.ast.values.len == 0) { |
| 7691 | 7692 | const case_src = case.ast.arrow_token - 1; |
| 7692 | 7693 | if (else_src) |src| { |
| ... | ... | @@ -7725,56 +7726,60 @@ fn switchExpr( |
| 7725 | 7726 | special_prong = .@"else"; |
| 7726 | 7727 | else_src = case_src; |
| 7727 | 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 | } |
| 7773 | 7730 | |
| 7731 | // Check for '_' prong. | |
| 7732 | var found_underscore = false; | |
| 7774 | 7733 | for (case.ast.values) |val| { |
| 7775 | if (tree.nodeTag(val) == .string_literal) | |
| 7776 | return astgen.failNode(val, "cannot switch on strings", .{}); | |
| 7734 | switch (tree.nodeTag(val)) { | |
| 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; | |
| 7778 | 7783 | |
| 7779 | 7784 | if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { |
| 7780 | 7785 | scalar_cases_len += 1; |
| ... | ... | @@ -7938,14 +7943,23 @@ fn switchExpr( |
| 7938 | 7943 | |
| 7939 | 7944 | const header_index: u32 = @intCast(payloads.items.len); |
| 7940 | 7945 | const body_len_index = if (is_multi_case) blk: { |
| 7941 | payloads.items[multi_case_table + multi_case_index] = header_index; | |
| 7942 | multi_case_index += 1; | |
| 7946 | if (case_node.toOptional() == special_node) { | |
| 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 | 7953 | try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len |
| 7944 | 7954 | |
| 7945 | 7955 | // items |
| 7946 | 7956 | var items_len: u32 = 0; |
| 7947 | 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 | 7963 | items_len += 1; |
| 7950 | 7964 | |
| 7951 | 7965 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); |
| ... | ... | @@ -7955,7 +7969,9 @@ fn switchExpr( |
| 7955 | 7969 | // ranges |
| 7956 | 7970 | var ranges_len: u32 = 0; |
| 7957 | 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 | 7975 | ranges_len += 1; |
| 7960 | 7976 | |
| 7961 | 7977 | const first_node, const last_node = tree.nodeData(range).node_and_node; |
| ... | ... | @@ -7970,6 +7986,7 @@ fn switchExpr( |
| 7970 | 7986 | payloads.items[header_index + 1] = ranges_len; |
| 7971 | 7987 | break :blk header_index + 2; |
| 7972 | 7988 | } else if (case_node.toOptional() == special_node) blk: { |
| 7989 | assert(special_prong != .absorbing_under); | |
| 7973 | 7990 | payloads.items[case_table_start] = header_index; |
| 7974 | 7991 | try payloads.resize(gpa, header_index + 1); // body_len |
| 7975 | 7992 | break :blk header_index; |
| ... | ... | @@ -8025,15 +8042,13 @@ fn switchExpr( |
| 8025 | 8042 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + |
| 8026 | 8043 | @intFromBool(multi_cases_len != 0) + |
| 8027 | 8044 | @intFromBool(any_has_tag_capture) + |
| 8028 | payloads.items.len - case_table_end + | |
| 8029 | (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len); | |
| 8045 | payloads.items.len - scratch_top); | |
| 8030 | 8046 | |
| 8031 | 8047 | const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ |
| 8032 | 8048 | .operand = raw_operand, |
| 8033 | 8049 | .bits = Zir.Inst.SwitchBlock.Bits{ |
| 8034 | 8050 | .has_multi_cases = multi_cases_len != 0, |
| 8035 | .has_else = special_prong == .@"else", | |
| 8036 | .has_under = special_prong == .under, | |
| 8051 | .special_prong = special_prong, | |
| 8037 | 8052 | .any_has_tag_capture = any_has_tag_capture, |
| 8038 | 8053 | .any_non_inline_capture = any_non_inline_capture, |
| 8039 | 8054 | .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, |
| ... | ... | @@ -8052,13 +8067,30 @@ fn switchExpr( |
| 8052 | 8067 | const zir_datas = astgen.instructions.items(.data); |
| 8053 | 8068 | zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; |
| 8054 | 8069 | |
| 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 | 8075 | var body_len_index = start_index; |
| 8057 | 8076 | var end_index = start_index; |
| 8058 | const table_index = case_table_start + i; | |
| 8059 | if (table_index < scalar_case_table) { | |
| 8077 | if (special_prong == .absorbing_under) { | |
| 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 | 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 | 8094 | body_len_index += 1; |
| 8063 | 8095 | end_index += 2; |
| 8064 | 8096 | } else { |
lib/std/zig/Zir.zig+41-19| ... | ... | @@ -3226,8 +3226,15 @@ pub const Inst = struct { |
| 3226 | 3226 | |
| 3227 | 3227 | /// 0. multi_cases_len: u32 // If has_multi_cases is set. |
| 3228 | 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 | 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 | 3238 | /// body member Index for every info.body_len |
| 3232 | 3239 | /// } |
| 3233 | 3240 | /// 3. scalar_cases: { // for every scalar_cases_len |
| ... | ... | @@ -3239,7 +3246,7 @@ pub const Inst = struct { |
| 3239 | 3246 | /// items_len: u32, |
| 3240 | 3247 | /// ranges_len: u32, |
| 3241 | 3248 | /// info: ProngInfo, |
| 3242 | /// item: Ref // for every items_len | |
| 3249 | /// item: Ref, // for every items_len | |
| 3243 | 3250 | /// ranges: { // for every ranges_len |
| 3244 | 3251 | /// item_first: Ref, |
| 3245 | 3252 | /// item_last: Ref, |
| ... | ... | @@ -3275,10 +3282,8 @@ pub const Inst = struct { |
| 3275 | 3282 | pub const Bits = packed struct(u32) { |
| 3276 | 3283 | /// If true, one or more prongs have multiple items. |
| 3277 | 3284 | has_multi_cases: bool, |
| 3278 | /// If true, there is an else prong. This is mutually exclusive with `has_under`. | |
| 3279 | has_else: bool, | |
| 3280 | /// If true, there is an underscore prong. This is mutually exclusive with `has_else`. | |
| 3281 | has_under: bool, | |
| 3285 | /// Information about the special prong. | |
| 3286 | special_prong: SpecialProng, | |
| 3282 | 3287 | /// If true, at least one prong has an inline tag capture. |
| 3283 | 3288 | any_has_tag_capture: bool, |
| 3284 | 3289 | /// If true, at least one prong has a capture which may not |
| ... | ... | @@ -3288,17 +3293,6 @@ pub const Inst = struct { |
| 3288 | 3293 | scalar_cases_len: ScalarCasesLen, |
| 3289 | 3294 | |
| 3290 | 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 | }; |
| 3303 | 3297 | |
| 3304 | 3298 | pub const MultiProng = struct { |
| ... | ... | @@ -3874,7 +3868,18 @@ pub const Inst = struct { |
| 3874 | 3868 | }; |
| 3875 | 3869 | }; |
| 3876 | 3870 | |
| 3877 | pub const SpecialProng = enum { none, @"else", under }; | |
| 3871 | pub 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 | }; | |
| 3878 | 3883 | |
| 3879 | 3884 | pub const DeclIterator = struct { |
| 3880 | 3885 | extra_index: u32, |
| ... | ... | @@ -4718,7 +4723,7 @@ fn findTrackableSwitch( |
| 4718 | 4723 | } |
| 4719 | 4724 | |
| 4720 | 4725 | const has_special = switch (kind) { |
| 4721 | .normal => extra.data.bits.specialProng() != .none, | |
| 4726 | .normal => extra.data.bits.special_prong != .none, | |
| 4722 | 4727 | .err_union => has_special: { |
| 4723 | 4728 | // Handle `non_err_body` first. |
| 4724 | 4729 | const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); |
| ... | ... | @@ -4733,6 +4738,23 @@ fn findTrackableSwitch( |
| 4733 | 4738 | }; |
| 4734 | 4739 | |
| 4735 | 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 | 4758 | const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]); |
| 4737 | 4759 | extra_index += 1; |
| 4738 | 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 | 11335 | var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len); |
| 11336 | 11336 | defer case_vals.deinit(gpa); |
| 11337 | 11337 | |
| 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 | 11342 | const special: SpecialProng = switch (special_prong) { |
| 11340 | 11343 | .none => .{ |
| 11341 | 11344 | .body = &.{}, |
| ... | ... | @@ -11355,6 +11358,26 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11355 | 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 | }; |
| 11359 | 11382 | |
| 11360 | 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 | 11398 | var else_error_ty: ?Type = null; |
| 11376 | 11399 | |
| 11377 | 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 | 11404 | const msg = msg: { |
| 11380 | 11405 | const msg = try sema.errMsg( |
| 11381 | 11406 | src, |
| ... | ... | @@ -11409,6 +11434,22 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11409 | 11434 | @memset(seen_enum_fields, null); |
| 11410 | 11435 | // `range_set` is used for non-exhaustive enum values that do not correspond to any tags. |
| 11411 | 11436 | |
| 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 | 11453 | var extra_index: usize = special.end; |
| 11413 | 11454 | { |
| 11414 | 11455 | var scalar_i: u32 = 0; |
| ... | ... | @@ -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 | 11737 | if (true_count + false_count < 2) { |
| 11697 | 11738 | return sema.fail( |
| 11698 | 11739 | block, |
| ... | ... | @@ -11892,7 +11933,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r |
| 11892 | 11933 | special.capture, |
| 11893 | 11934 | block.src(.{ .switch_capture = .{ |
| 11894 | 11935 | .switch_node_offset = src_node_offset, |
| 11895 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 11936 | .case_idx = .special, | |
| 11896 | 11937 | } }), |
| 11897 | 11938 | undefined, // case_vals may be undefined for special prongs |
| 11898 | 11939 | .none, |
| ... | ... | @@ -12344,7 +12385,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12344 | 12385 | special.capture, |
| 12345 | 12386 | child_block.src(.{ .switch_capture = .{ |
| 12346 | 12387 | .switch_node_offset = switch_node_offset, |
| 12347 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12388 | .case_idx = .special, | |
| 12348 | 12389 | } }), |
| 12349 | 12390 | &.{item_ref}, |
| 12350 | 12391 | item_ref, |
| ... | ... | @@ -12399,7 +12440,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12399 | 12440 | special.capture, |
| 12400 | 12441 | child_block.src(.{ .switch_capture = .{ |
| 12401 | 12442 | .switch_node_offset = switch_node_offset, |
| 12402 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12443 | .case_idx = .special, | |
| 12403 | 12444 | } }), |
| 12404 | 12445 | &.{item_ref}, |
| 12405 | 12446 | item_ref, |
| ... | ... | @@ -12439,7 +12480,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12439 | 12480 | special.capture, |
| 12440 | 12481 | child_block.src(.{ .switch_capture = .{ |
| 12441 | 12482 | .switch_node_offset = switch_node_offset, |
| 12442 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12483 | .case_idx = .special, | |
| 12443 | 12484 | } }), |
| 12444 | 12485 | &.{item_ref}, |
| 12445 | 12486 | item_ref, |
| ... | ... | @@ -12476,7 +12517,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12476 | 12517 | special.capture, |
| 12477 | 12518 | child_block.src(.{ .switch_capture = .{ |
| 12478 | 12519 | .switch_node_offset = switch_node_offset, |
| 12479 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12520 | .case_idx = .special, | |
| 12480 | 12521 | } }), |
| 12481 | 12522 | &.{.bool_true}, |
| 12482 | 12523 | .bool_true, |
| ... | ... | @@ -12511,7 +12552,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12511 | 12552 | special.capture, |
| 12512 | 12553 | child_block.src(.{ .switch_capture = .{ |
| 12513 | 12554 | .switch_node_offset = switch_node_offset, |
| 12514 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12555 | .case_idx = .special, | |
| 12515 | 12556 | } }), |
| 12516 | 12557 | &.{.bool_false}, |
| 12517 | 12558 | .bool_false, |
| ... | ... | @@ -12571,7 +12612,7 @@ fn analyzeSwitchRuntimeBlock( |
| 12571 | 12612 | special.capture, |
| 12572 | 12613 | child_block.src(.{ .switch_capture = .{ |
| 12573 | 12614 | .switch_node_offset = switch_node_offset, |
| 12574 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12615 | .case_idx = .special, | |
| 12575 | 12616 | } }), |
| 12576 | 12617 | undefined, // case_vals may be undefined for special prongs |
| 12577 | 12618 | .none, |
| ... | ... | @@ -12836,7 +12877,7 @@ fn resolveSwitchComptime( |
| 12836 | 12877 | special.capture, |
| 12837 | 12878 | child_block.src(.{ .switch_capture = .{ |
| 12838 | 12879 | .switch_node_offset = switch_node_offset, |
| 12839 | .case_idx = LazySrcLoc.Offset.SwitchCaseIndex.special, | |
| 12880 | .case_idx = .special, | |
| 12840 | 12881 | } }), |
| 12841 | 12882 | undefined, // case_vals may be undefined for special prongs |
| 12842 | 12883 | if (special.is_inline) cond_operand else .none, |
src/Zcu.zig+26-21| ... | ... | @@ -1684,13 +1684,13 @@ pub const SrcLoc = struct { |
| 1684 | 1684 | const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index); |
| 1685 | 1685 | for (case_nodes) |case_node| { |
| 1686 | 1686 | const case = tree.fullSwitchCase(case_node).?; |
| 1687 | const is_special = (case.ast.values.len == 0) or | |
| 1688 | (case.ast.values.len == 1 and | |
| 1689 | tree.nodeTag(case.ast.values[0]) == .identifier and | |
| 1690 | mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")); | |
| 1691 | if (!is_special) continue; | |
| 1692 | ||
| 1693 | return tree.nodeToSpan(case_node); | |
| 1687 | if (case.isSpecial(tree)) |special_node| { | |
| 1688 | return tree.tokensToSpan( | |
| 1689 | tree.firstToken(case_node), | |
| 1690 | tree.lastToken(case_node), | |
| 1691 | tree.nodeMainToken(special_node.unwrap() orelse case_node), | |
| 1692 | ); | |
| 1693 | } | |
| 1694 | 1694 | } else unreachable; |
| 1695 | 1695 | }, |
| 1696 | 1696 | |
| ... | ... | @@ -1701,11 +1701,9 @@ pub const SrcLoc = struct { |
| 1701 | 1701 | const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index); |
| 1702 | 1702 | for (case_nodes) |case_node| { |
| 1703 | 1703 | const case = tree.fullSwitchCase(case_node).?; |
| 1704 | const is_special = (case.ast.values.len == 0) or | |
| 1705 | (case.ast.values.len == 1 and | |
| 1706 | tree.nodeTag(case.ast.values[0]) == .identifier and | |
| 1707 | mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")); | |
| 1708 | if (is_special) continue; | |
| 1704 | if (case.isSpecial(tree)) |maybe_else| { | |
| 1705 | if (maybe_else == .none) continue; | |
| 1706 | } | |
| 1709 | 1707 | |
| 1710 | 1708 | for (case.ast.values) |item_node| { |
| 1711 | 1709 | if (tree.nodeTag(item_node) == .switch_range) { |
| ... | ... | @@ -2111,17 +2109,21 @@ pub const SrcLoc = struct { |
| 2111 | 2109 | |
| 2112 | 2110 | var multi_i: u32 = 0; |
| 2113 | 2111 | var scalar_i: u32 = 0; |
| 2112 | var found_special = false; | |
| 2113 | var underscore_node: Ast.Node.OptionalIndex = .none; | |
| 2114 | 2114 | const case = for (case_nodes) |case_node| { |
| 2115 | 2115 | const case = tree.fullSwitchCase(case_node).?; |
| 2116 | 2116 | const is_special = special: { |
| 2117 | if (case.ast.values.len == 0) break :special true; | |
| 2118 | if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .identifier) { | |
| 2119 | break :special mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"); | |
| 2117 | if (found_special) break :special false; | |
| 2118 | if (case.isSpecial(tree)) |special_node| { | |
| 2119 | underscore_node = special_node; | |
| 2120 | found_special = true; | |
| 2121 | break :special true; | |
| 2120 | 2122 | } |
| 2121 | 2123 | break :special false; |
| 2122 | 2124 | }; |
| 2123 | 2125 | if (is_special) { |
| 2124 | if (want_case_idx.isSpecial()) { | |
| 2126 | if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special) { | |
| 2125 | 2127 | break case; |
| 2126 | 2128 | } |
| 2127 | 2129 | continue; |
| ... | ... | @@ -2171,7 +2173,11 @@ pub const SrcLoc = struct { |
| 2171 | 2173 | .single => { |
| 2172 | 2174 | var item_i: u32 = 0; |
| 2173 | 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 | 2181 | if (item_i != want_item.index) { |
| 2176 | 2182 | item_i += 1; |
| 2177 | 2183 | continue; |
| ... | ... | @@ -2182,7 +2188,9 @@ pub const SrcLoc = struct { |
| 2182 | 2188 | .range => { |
| 2183 | 2189 | var range_i: u32 = 0; |
| 2184 | 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 | 2194 | if (range_i != want_item.index) { |
| 2187 | 2195 | range_i += 1; |
| 2188 | 2196 | continue; |
| ... | ... | @@ -2561,9 +2569,6 @@ pub const LazySrcLoc = struct { |
| 2561 | 2569 | index: u31, |
| 2562 | 2570 | |
| 2563 | 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 | }; |
| 2568 | 2573 | |
| 2569 | 2574 | pub const SwitchItemIndex = packed struct(u32) { |
src/print_zir.zig+45-15| ... | ... | @@ -2088,27 +2088,57 @@ const Writer = struct { |
| 2088 | 2088 | self.indent += 2; |
| 2089 | 2089 | |
| 2090 | 2090 | else_prong: { |
| 2091 | const special_prong = extra.data.bits.specialProng(); | |
| 2092 | const prong_name = switch (special_prong) { | |
| 2093 | .@"else" => "else", | |
| 2094 | .under => "_", | |
| 2095 | else => break :else_prong, | |
| 2096 | }; | |
| 2091 | const special_prong = extra.data.bits.special_prong; | |
| 2092 | if (special_prong == .none) break :else_prong; | |
| 2097 | 2093 | |
| 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 | 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 | 2103 | extra_index += 1; |
| 2106 | const body = self.code.bodySlice(extra_index, info.body_len); | |
| 2107 | extra_index += body.len; | |
| 2104 | const items = self.code.refSlice(extra_index, items_len); | |
| 2105 | extra_index += items_len; | |
| 2108 | 2106 | |
| 2109 | 2107 | try stream.writeAll(",\n"); |
| 2110 | 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 | 2142 | try self.writeBracedBody(stream, body); |
| 2113 | 2143 | } |
| 2114 | 2144 |
test/behavior/switch.zig+25| ... | ... | @@ -1073,3 +1073,28 @@ test "switch on 8-bit mod result" { |
| 1073 | 1073 | else => unreachable, |
| 1074 | 1074 | } |
| 1075 | 1075 | } |
| 1076 | ||
| 1077 | test "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 @@ |
| 1 | const E = enum(u8) { | |
| 2 | a, | |
| 3 | b, | |
| 4 | _, | |
| 5 | }; | |
| 6 | const U = union(E) { | |
| 7 | a: i32, | |
| 8 | b: u32, | |
| 9 | }; | |
| 10 | pub export fn entry1() void { | |
| 11 | const e: E = .b; | |
| 12 | switch (e) { // error: switch not handling the tag `b` | |
| 13 | .a, _ => {}, | |
| 14 | } | |
| 15 | } | |
| 16 | pub 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 | 16 | // target=native |
| 17 | 17 | // |
| 18 | 18 | // :7:5: error: '_' prong only allowed when switching on non-exhaustive enums |
| 19 | // :10:11: note: '_' prong here | |
| 19 | // :10:9: note: '_' prong here | |
| 20 | 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 | 39 | // :1:11: note: enum 'tmp.E' declared here |
| 40 | 40 | // :19:5: error: switch on non-exhaustive enum must include 'else' or '_' prong |
| 41 | 41 | // :26:5: error: '_' prong only allowed when switching on non-exhaustive enums |
| 42 | // :29:11: note: '_' prong here | |
| 42 | // :29:9: note: '_' prong here | |
| 43 | 43 | // :26:5: note: consider using 'else' |