authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 16:39:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 16:39:10-07:00
log8f28e26e7a4f770f8d8e700386e2ade111948891
treed96d35bdf5e3e530b0dd42fd30114af79b916fd5
parentccdba774c84e2b3c7f650e27f430c9a3eed78fd3

Sema: implement switch validation for enums


3 files changed, 238 insertions(+), 25 deletions(-)

src/Sema.zig+169-24
......@@ -2729,6 +2729,8 @@ fn analyzeSwitch(
27292729 src_node_offset: i32,
27302730) InnerError!*Inst {
27312731 const gpa = sema.gpa;
2732 const mod = sema.mod;
2733
27322734 const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
27332735 .none => .{ .body = &.{}, .end = extra_end },
27342736 .under, .@"else" => blk: {
......@@ -2748,14 +2750,14 @@ fn analyzeSwitch(
27482750 // Validate usage of '_' prongs.
27492751 if (special_prong == .under and !operand.ty.isExhaustiveEnum()) {
27502752 const msg = msg: {
2751 const msg = try sema.mod.errMsg(
2753 const msg = try mod.errMsg(
27522754 &block.base,
27532755 src,
27542756 "'_' prong only allowed when switching on non-exhaustive enums",
27552757 .{},
27562758 );
27572759 errdefer msg.destroy(gpa);
2758 try sema.mod.errNote(
2760 try mod.errNote(
27592761 &block.base,
27602762 special_prong_src,
27612763 msg,
......@@ -2764,14 +2766,121 @@ fn analyzeSwitch(
27642766 );
27652767 break :msg msg;
27662768 };
2767 return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
2769 return mod.failWithOwnedErrorMsg(&block.base, msg);
27682770 }
27692771
27702772 // Validate for duplicate items, missing else prong, and invalid range.
27712773 switch (operand.ty.zigTypeTag()) {
2772 .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}),
2773 .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
2774 .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
2774 .Enum => {
2775 var seen_fields = try gpa.alloc(?AstGen.SwitchProngSrc, operand.ty.enumFieldCount());
2776 defer gpa.free(seen_fields);
2777
2778 var extra_index: usize = special.end;
2779 {
2780 var scalar_i: u32 = 0;
2781 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2782 const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
2783 extra_index += 1;
2784 const body_len = sema.code.extra[extra_index];
2785 extra_index += 1;
2786 const body = sema.code.extra[extra_index..][0..body_len];
2787 extra_index += body_len;
2788
2789 try sema.validateSwitchItemEnum(
2790 block,
2791 seen_fields,
2792 item_ref,
2793 src_node_offset,
2794 .{ .scalar = scalar_i },
2795 );
2796 }
2797 }
2798 {
2799 var multi_i: u32 = 0;
2800 while (multi_i < multi_cases_len) : (multi_i += 1) {
2801 const items_len = sema.code.extra[extra_index];
2802 extra_index += 1;
2803 const ranges_len = sema.code.extra[extra_index];
2804 extra_index += 1;
2805 const body_len = sema.code.extra[extra_index];
2806 extra_index += 1;
2807 const items = sema.code.refSlice(extra_index, items_len);
2808 extra_index += items_len + body_len;
2809
2810 for (items) |item_ref, item_i| {
2811 try sema.validateSwitchItemEnum(
2812 block,
2813 seen_fields,
2814 item_ref,
2815 src_node_offset,
2816 .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
2817 );
2818 }
2819
2820 try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
2821 }
2822 }
2823 const all_tags_handled = for (seen_fields) |seen_src| {
2824 if (seen_src == null) break false;
2825 } else true;
2826
2827 switch (special_prong) {
2828 .none => {
2829 if (!all_tags_handled) {
2830 const msg = msg: {
2831 const msg = try mod.errMsg(
2832 &block.base,
2833 src,
2834 "switch must handle all possibilities",
2835 .{},
2836 );
2837 errdefer msg.destroy(sema.gpa);
2838 try mod.errNoteNonLazy(
2839 operand.ty.declSrcLoc(),
2840 msg,
2841 "enum '{}' declared here",
2842 .{operand.ty},
2843 );
2844 for (seen_fields) |seen_src, i| {
2845 if (seen_src != null) continue;
2846
2847 const field_name = operand.ty.enumFieldName(i);
2848
2849 // TODO have this point to the tag decl instead of here
2850 try mod.errNote(
2851 &block.base,
2852 src,
2853 msg,
2854 "unhandled enumeration value: '{s}",
2855 .{field_name},
2856 );
2857 }
2858 break :msg msg;
2859 };
2860 return mod.failWithOwnedErrorMsg(&block.base, msg);
2861 }
2862 },
2863 .under => {
2864 if (all_tags_handled) return mod.fail(
2865 &block.base,
2866 special_prong_src,
2867 "unreachable '_' prong; all cases already handled",
2868 .{},
2869 );
2870 },
2871 .@"else" => {
2872 if (all_tags_handled) return mod.fail(
2873 &block.base,
2874 special_prong_src,
2875 "unreachable else prong; all cases already handled",
2876 .{},
2877 );
2878 },
2879 }
2880 },
2881
2882 .ErrorSet => return mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
2883 .Union => return mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
27752884 .Int, .ComptimeInt => {
27762885 var range_set = RangeSet.init(gpa);
27772886 defer range_set.deinit();
......@@ -2844,11 +2953,11 @@ fn analyzeSwitch(
28442953 var arena = std.heap.ArenaAllocator.init(gpa);
28452954 defer arena.deinit();
28462955
2847 const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget());
2848 const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget());
2956 const min_int = try operand.ty.minInt(&arena, mod.getTarget());
2957 const max_int = try operand.ty.maxInt(&arena, mod.getTarget());
28492958 if (try range_set.spans(min_int, max_int)) {
28502959 if (special_prong == .@"else") {
2851 return sema.mod.fail(
2960 return mod.fail(
28522961 &block.base,
28532962 special_prong_src,
28542963 "unreachable else prong; all cases already handled",
......@@ -2859,7 +2968,7 @@ fn analyzeSwitch(
28592968 }
28602969 }
28612970 if (special_prong != .@"else") {
2862 return sema.mod.fail(
2971 return mod.fail(
28632972 &block.base,
28642973 src,
28652974 "switch must handle all possibilities",
......@@ -2922,7 +3031,7 @@ fn analyzeSwitch(
29223031 switch (special_prong) {
29233032 .@"else" => {
29243033 if (true_count + false_count == 2) {
2925 return sema.mod.fail(
3034 return mod.fail(
29263035 &block.base,
29273036 src,
29283037 "unreachable else prong; all cases already handled",
......@@ -2932,7 +3041,7 @@ fn analyzeSwitch(
29323041 },
29333042 .under, .none => {
29343043 if (true_count + false_count < 2) {
2935 return sema.mod.fail(
3044 return mod.fail(
29363045 &block.base,
29373046 src,
29383047 "switch must handle all possibilities",
......@@ -2944,7 +3053,7 @@ fn analyzeSwitch(
29443053 },
29453054 .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
29463055 if (special_prong != .@"else") {
2947 return sema.mod.fail(
3056 return mod.fail(
29483057 &block.base,
29493058 src,
29503059 "else prong required when switching on type '{}'",
......@@ -3016,7 +3125,7 @@ fn analyzeSwitch(
30163125 .AnyFrame,
30173126 .ComptimeFloat,
30183127 .Float,
3019 => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
3128 => return mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
30203129 operand.ty,
30213130 }),
30223131 }
......@@ -3291,7 +3400,7 @@ fn resolveSwitchItemVal(
32913400 switch_node_offset: i32,
32923401 switch_prong_src: AstGen.SwitchProngSrc,
32933402 range_expand: AstGen.SwitchProngSrc.RangeExpand,
3294) InnerError!Value {
3403) InnerError!TypedValue {
32953404 const item = try sema.resolveInst(item_ref);
32963405 // We have to avoid the other helper functions here because we cannot construct a LazySrcLoc
32973406 // because we only have the switch AST node. Only if we know for sure we need to report
......@@ -3301,7 +3410,7 @@ fn resolveSwitchItemVal(
33013410 const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
33023411 return sema.failWithUseOfUndef(block, src);
33033412 }
3304 return val;
3413 return TypedValue{ .ty = item.ty, .val = val };
33053414 }
33063415 const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
33073416 return sema.failWithNeededComptime(block, src);
......@@ -3316,8 +3425,8 @@ fn validateSwitchRange(
33163425 src_node_offset: i32,
33173426 switch_prong_src: AstGen.SwitchProngSrc,
33183427) InnerError!void {
3319 const first_val = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first);
3320 const last_val = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last);
3428 const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val;
3429 const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val;
33213430 const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src);
33223431 return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
33233432}
......@@ -3330,11 +3439,46 @@ fn validateSwitchItem(
33303439 src_node_offset: i32,
33313440 switch_prong_src: AstGen.SwitchProngSrc,
33323441) InnerError!void {
3333 const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
3442 const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
33343443 const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src);
33353444 return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
33363445}
33373446
3447fn validateSwitchItemEnum(
3448 sema: *Sema,
3449 block: *Scope.Block,
3450 seen_fields: []?AstGen.SwitchProngSrc,
3451 item_ref: zir.Inst.Ref,
3452 src_node_offset: i32,
3453 switch_prong_src: AstGen.SwitchProngSrc,
3454) InnerError!void {
3455 const mod = sema.mod;
3456 const item_tv = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
3457 const field_index = item_tv.ty.enumTagFieldIndex(item_tv.val) orelse {
3458 const msg = msg: {
3459 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
3460 const msg = try mod.errMsg(
3461 &block.base,
3462 src,
3463 "enum '{}' has no tag with value '{}'",
3464 .{ item_tv.ty, item_tv.val },
3465 );
3466 errdefer msg.destroy(sema.gpa);
3467 try mod.errNoteNonLazy(
3468 item_tv.ty.declSrcLoc(),
3469 msg,
3470 "enum declared here",
3471 .{},
3472 );
3473 break :msg msg;
3474 };
3475 return mod.failWithOwnedErrorMsg(&block.base, msg);
3476 };
3477 const maybe_prev_src = seen_fields[field_index];
3478 seen_fields[field_index] = switch_prong_src;
3479 return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
3480}
3481
33383482fn validateSwitchDupe(
33393483 sema: *Sema,
33403484 block: *Scope.Block,
......@@ -3343,17 +3487,18 @@ fn validateSwitchDupe(
33433487 src_node_offset: i32,
33443488) InnerError!void {
33453489 const prev_prong_src = maybe_prev_src orelse return;
3490 const mod = sema.mod;
33463491 const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
33473492 const prev_src = prev_prong_src.resolve(block.src_decl, src_node_offset, .none);
33483493 const msg = msg: {
3349 const msg = try sema.mod.errMsg(
3494 const msg = try mod.errMsg(
33503495 &block.base,
33513496 src,
33523497 "duplicate switch value",
33533498 .{},
33543499 );
33553500 errdefer msg.destroy(sema.gpa);
3356 try sema.mod.errNote(
3501 try mod.errNote(
33573502 &block.base,
33583503 prev_src,
33593504 msg,
......@@ -3362,7 +3507,7 @@ fn validateSwitchDupe(
33623507 );
33633508 break :msg msg;
33643509 };
3365 return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
3510 return mod.failWithOwnedErrorMsg(&block.base, msg);
33663511}
33673512
33683513fn validateSwitchItemBool(
......@@ -3374,7 +3519,7 @@ fn validateSwitchItemBool(
33743519 src_node_offset: i32,
33753520 switch_prong_src: AstGen.SwitchProngSrc,
33763521) InnerError!void {
3377 const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
3522 const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
33783523 if (item_val.toBool()) {
33793524 true_count.* += 1;
33803525 } else {
......@@ -3396,7 +3541,7 @@ fn validateSwitchItemSparse(
33963541 src_node_offset: i32,
33973542 switch_prong_src: AstGen.SwitchProngSrc,
33983543) InnerError!void {
3399 const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
3544 const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
34003545 const entry = (try seen_values.fetchPut(item_val, switch_prong_src)) orelse return;
34013546 return sema.validateSwitchDupe(block, entry.value, switch_prong_src, src_node_offset);
34023547}
src/type.zig+64
......@@ -2126,6 +2126,34 @@ pub const Type = extern union {
21262126 };
21272127 }
21282128
2129 pub fn enumFieldCount(ty: Type) usize {
2130 switch (ty.tag()) {
2131 .enum_full, .enum_nonexhaustive => {
2132 const enum_full = ty.cast(Payload.EnumFull).?.data;
2133 return enum_full.fields.count();
2134 },
2135 .enum_simple => {
2136 const enum_simple = ty.castTag(.enum_simple).?.data;
2137 return enum_simple.fields.count();
2138 },
2139 else => unreachable,
2140 }
2141 }
2142
2143 pub fn enumFieldName(ty: Type, field_index: usize) []const u8 {
2144 switch (ty.tag()) {
2145 .enum_full, .enum_nonexhaustive => {
2146 const enum_full = ty.cast(Payload.EnumFull).?.data;
2147 return enum_full.fields.entries.items[field_index].key;
2148 },
2149 .enum_simple => {
2150 const enum_simple = ty.castTag(.enum_simple).?.data;
2151 return enum_simple.fields.entries.items[field_index].key;
2152 },
2153 else => unreachable,
2154 }
2155 }
2156
21292157 pub fn enumFieldIndex(ty: Type, field_name: []const u8) ?usize {
21302158 switch (ty.tag()) {
21312159 .enum_full, .enum_nonexhaustive => {
......@@ -2140,6 +2168,42 @@ pub const Type = extern union {
21402168 }
21412169 }
21422170
2171 /// Asserts `ty` is an enum. `enum_tag` can either be `enum_field_index` or
2172 /// an integer which represents the enum value. Returns the field index in
2173 /// declaration order, or `null` if `enum_tag` does not match any field.
2174 pub fn enumTagFieldIndex(ty: Type, enum_tag: Value) ?usize {
2175 if (enum_tag.castTag(.enum_field_index)) |payload| {
2176 return @as(usize, payload.data);
2177 }
2178 const S = struct {
2179 fn fieldWithRange(int_val: Value, end: usize) ?usize {
2180 if (int_val.compareWithZero(.lt)) return null;
2181 var end_payload: Value.Payload.U64 = .{
2182 .base = .{ .tag = .int_u64 },
2183 .data = end,
2184 };
2185 const end_val = Value.initPayload(&end_payload.base);
2186 if (int_val.compare(.gte, end_val)) return null;
2187 return int_val.toUnsignedInt();
2188 }
2189 };
2190 switch (ty.tag()) {
2191 .enum_full, .enum_nonexhaustive => {
2192 const enum_full = ty.cast(Payload.EnumFull).?.data;
2193 if (enum_full.values.count() == 0) {
2194 return S.fieldWithRange(enum_tag, enum_full.fields.count());
2195 } else {
2196 return enum_full.values.getIndex(enum_tag);
2197 }
2198 },
2199 .enum_simple => {
2200 const enum_simple = ty.castTag(.enum_simple).?.data;
2201 return S.fieldWithRange(enum_tag, enum_simple.fields.count());
2202 },
2203 else => unreachable,
2204 }
2205 }
2206
21432207 pub fn declSrcLoc(ty: Type) Module.SrcLoc {
21442208 switch (ty.tag()) {
21452209 .enum_full, .enum_nonexhaustive => {
test/stage2/cbe.zig+5-1
......@@ -552,7 +552,11 @@ pub fn addCases(ctx: *TestContext) !void {
552552 \\ if (@enumToInt(number3) != 2) return 1;
553553 \\ var x: Number = .Two;
554554 \\ if (number2 != x) return 1;
555 \\ return 0;
555 \\ switch (x) {
556 \\ .One => return 1,
557 \\ .Two => return 0,
558 \\ number3 => return 2,
559 \\ }
556560 \\}
557561 , "");
558562 }