authorgravatar for justusk@noreply.codeberg.orgJustus Klausecker <justusk@noreply.codeberg.org> 2026-07-25 00:31:06+02:00
committergravatar for justusk@noreply.codeberg.orgJustus Klausecker <justusk@noreply.codeberg.org> 2026-07-25 00:31:06+02:00
log783ad796565947e07430d0c73ab279020e5d9e74
treec37b1b46533fc05da67f1602d07d447d1031b35b
parent8b2d0ce218db8d874cec1c11b3f186955af620c1
parentc1682a01c1c3994b3a30173197954ba65992bee1

Merge pull request 'frontend: improve `switch` compile errors' (#35390) from justusk/zig:switch-err-msg into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35390

17 files changed, 402 insertions(+), 215 deletions(-)

lib/std/zig/AstGen.zig+1
......@@ -7453,6 +7453,7 @@ fn switchExpr(
74537453 const ident_name = try astgen.identAsString(ident_token);
74547454 const ident_name_str = tree.tokenSlice(ident_token);
74557455 if (mem.eql(u8, "_", ident_name_str)) {
7456 if (non_err_is_ref != .no) return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
74567457 break :scope &scratch_scope.base;
74577458 }
74587459 non_err_capture = if (non_err_is_ref != .no) .by_ref else .by_val;
src/RangeSet.zig+18-23
......@@ -1,6 +1,6 @@
11const RangeSet = @This();
22
3ranges: std.MultiArrayList(Range),
3list: std.MultiArrayList(Range),
44
55pub const Range = struct {
66 first: Value,
......@@ -8,41 +8,36 @@ pub const Range = struct {
88 src: LazySrcLoc,
99};
1010
11pub const empty: RangeSet = .{ .ranges = .empty };
11pub const empty: RangeSet = .{ .list = .empty };
1212
1313pub fn deinit(self: *RangeSet, allocator: Allocator) void {
14 self.ranges.deinit(allocator);
14 self.list.deinit(allocator);
1515 self.* = undefined;
1616}
1717
18pub fn ensureUnusedCapacity(self: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {
19 return self.ranges.ensureUnusedCapacity(allocator, additional_count);
18pub fn ensureUnusedCapacity(set: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {
19 return set.list.ensureUnusedCapacity(allocator, additional_count);
2020}
2121
22pub fn addAssumeCapacity(set: *RangeSet, new: Range, ty: Type, zcu: *Zcu) ?LazySrcLoc {
22pub fn addAssumeCapacity(set: *RangeSet, new: Range, ty: Type, zcu: *Zcu) ?Range {
2323 assert(new.first.typeOf(zcu).eql(ty));
2424 assert(new.last.typeOf(zcu).eql(ty));
2525 assert(new.first.compareScalar(.lte, new.last, ty, zcu));
2626
27 const idx = std.sort.lowerBound(Value, set.ranges.items(.last), @as(SearchCtx, .{
27 const idx = std.sort.lowerBound(Value, set.list.items(.last), @as(SearchCtx, .{
2828 .val = new.first,
2929 .zcu = zcu,
3030 }), compare);
3131
32 if (idx != set.ranges.len and // `new.first` is *not* greater than all `old.last`
33 new.last.compareScalar(.gte, set.ranges.items(.first)[idx], ty, zcu))
32 if (idx != set.list.len and // `new.first` is *not* greater than all `old.last`
33 new.last.compareScalar(.gte, set.list.items(.first)[idx], ty, zcu))
3434 {
35 return set.ranges.items(.src)[idx]; // `new` overlaps with existing range.
35 return set.list.get(idx); // `new` overlaps with existing range.
3636 }
37 set.ranges.insertAssumeCapacity(idx, new);
37 set.list.insertAssumeCapacity(idx, new);
3838 return null;
3939}
4040
41pub fn add(set: *RangeSet, allocator: Allocator, new: Range, ty: Type, zcu: *Zcu) Allocator.Error!?LazySrcLoc {
42 try set.ensureUnusedCapacity(allocator, 1);
43 return set.addAssumeCapacity(new, ty, zcu);
44}
45
4641pub fn spans(
4742 set: *RangeSet,
4843 allocator: Allocator,
......@@ -53,13 +48,13 @@ pub fn spans(
5348) Allocator.Error!bool {
5449 assert(first.typeOf(zcu).eql(ty));
5550 assert(last.typeOf(zcu).eql(ty));
56 if (set.ranges.len == 0) return false;
51 if (set.list.len == 0) return false;
5752
58 assert(std.sort.isSorted(Value, set.ranges.items(.first), @as(SortCtx, .{ .ty = ty, .zcu = zcu }), lessThan));
59 assert(std.sort.isSorted(Value, set.ranges.items(.last), @as(SortCtx, .{ .ty = ty, .zcu = zcu }), lessThan));
53 assert(std.sort.isSorted(Value, set.list.items(.first), @as(SortCtx, .{ .ty = ty, .zcu = zcu }), lessThan));
54 assert(std.sort.isSorted(Value, set.list.items(.last), @as(SortCtx, .{ .ty = ty, .zcu = zcu }), lessThan));
6055
61 if (!set.ranges.items(.first)[0].eql(first, ty, zcu) or
62 !set.ranges.items(.last)[set.ranges.len - 1].eql(last, ty, zcu))
56 if (!set.list.items(.first)[0].eql(first, ty, zcu) or
57 !set.list.items(.last)[set.list.len - 1].eql(last, ty, zcu))
6358 {
6459 return false;
6560 }
......@@ -75,8 +70,8 @@ pub fn spans(
7570
7671 // look for gaps
7772 for (
78 set.ranges.items(.first)[1..],
79 set.ranges.items(.last)[0 .. set.ranges.len - 1],
73 set.list.items(.first)[1..],
74 set.list.items(.last)[0 .. set.list.len - 1],
8075 ) |cur_first, prev_last| {
8176 // prev_last + 1 == cur_first
8277 counter.copy(prev_last.toBigInt(&space, zcu));
src/Sema.zig+169-134
......@@ -10760,7 +10760,7 @@ fn finishSwitchBr(
1076010760 .@"enum" => if (else_is_named_only or
1076110761 !item_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally)
1076210762 {
10763 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
10763 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen.enum_fields.len));
1076410764 break :check_enumerable .{ undefined, undefined };
1076510765 },
1076610766 .error_set => if (!operand_ty.isAnyError(zcu)) {
......@@ -10881,13 +10881,13 @@ fn finishSwitchBr(
1088110881 try branch_hints.append(gpa, prong_hint);
1088210882
1088310883 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
10884 (validated_switch.seen_enum_fields.len + 1 - zir_switch.totalItemsLen()) + // +1 because totalItemsLen includes the _
10884 (validated_switch.seen.enum_fields.len + 1 - zir_switch.totalItemsLen()) + // +1 because totalItemsLen includes the _
1088510885 case_block.instructions.items.len);
1088610886 const extra_case = cases_extra.addManyAsArrayAssumeCapacity(
1088710887 @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len,
1088810888 );
1088910889 var items_len: u32 = 0;
10890 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
10890 for (validated_switch.seen.enum_fields, 0..) |seen_field, field_i| {
1089110891 if (seen_field != null) continue;
1089210892 const item_val = try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
1089310893 const item_ref: Air.Inst.Ref = .fromValue(item_val);
......@@ -10920,7 +10920,7 @@ fn finishSwitchBr(
1092010920 }
1092110921 if (tagged_union_originally) {
1092210922 const union_obj = zcu.typeToUnion(operand_ty).?;
10923 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
10923 for (validated_switch.seen.enum_fields, 0..) |seen_field, field_i| {
1092410924 if (seen_field != null) continue;
1092510925 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_i]);
1092610926 if (!field_ty.isNoReturn(zcu)) break :analyze_body true;
......@@ -11004,17 +11004,21 @@ fn finishSwitchBr(
1100411004}
1100511005
1100611006const ValidatedSwitchBlock = struct {
11007 seen_enum_fields: []const ?LazySrcLoc,
11008 seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11009 seen_ranges: std.MultiArrayList(RangeSet.Range).Slice,
11010 true_src: ?LazySrcLoc,
11011 false_src: ?LazySrcLoc,
11012 void_src: ?LazySrcLoc,
11013
11007 seen: Seen,
1101411008 case_vals: []const Air.Inst.Ref,
1101511009 else_case: Zir.UnwrappedSwitchBlock.Case.Else,
1101611010 else_err_ty: ?Type,
1101711011
11012 const Seen = struct {
11013 enum_fields: []?LazySrcLoc,
11014 errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11015 sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
11016 ranges: RangeSet,
11017 true_src: ?LazySrcLoc,
11018 false_src: ?LazySrcLoc,
11019 void_src: ?LazySrcLoc,
11020 };
11021
1101811022 fn iterateUnhandledItems(
1101911023 validated_switch: *const ValidatedSwitchBlock,
1102011024 /// May be `undefined` if `item_ty` isn't an `error_set`.
......@@ -11023,28 +11027,26 @@ const ValidatedSwitchBlock = struct {
1102311027 min_int: Value,
1102411028 ) UnhandledIterator {
1102511029 return .{
11030 .error_names = error_names,
11031 .seen = &validated_switch.seen,
11032
1102611033 .next_idx = 0,
1102711034 .next_val = min_int,
11028 .error_names = error_names,
11029 .seen_enum_fields = validated_switch.seen_enum_fields,
11030 .seen_errors = &validated_switch.seen_errors,
11031 .seen_ranges = validated_switch.seen_ranges,
11032 .seen_true = validated_switch.true_src != null,
11033 .seen_false = validated_switch.false_src != null,
11034 .seen_void = validated_switch.void_src != null,
11035 .handled_true = validated_switch.seen.true_src != null,
11036 .handled_false = validated_switch.seen.false_src != null,
11037 .handled_void = validated_switch.seen.void_src != null,
1103511038 };
1103611039 }
1103711040
1103811041 const UnhandledIterator = struct {
11042 error_names: InternPool.NullTerminatedString.Slice,
11043 seen: *const Seen,
11044
1103911045 next_idx: u32,
1104011046 next_val: ?Value,
11041 error_names: InternPool.NullTerminatedString.Slice,
11042 seen_enum_fields: []const ?LazySrcLoc,
11043 seen_errors: *const std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11044 seen_ranges: std.MultiArrayList(RangeSet.Range).Slice,
11045 seen_true: bool,
11046 seen_false: bool,
11047 seen_void: bool,
11047 handled_true: bool,
11048 handled_false: bool,
11049 handled_void: bool,
1104811050
1104911051 fn next(it: *UnhandledIterator, sema: *Sema, item_ty: Type) CompileError!?Value {
1105011052 const pt = sema.pt;
......@@ -11052,7 +11054,7 @@ const ValidatedSwitchBlock = struct {
1105211054 const ip = &zcu.intern_pool;
1105311055 switch (item_ty.zigTypeTag(zcu)) {
1105411056 .@"enum" => {
11055 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
11057 for (it.seen.enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
1105611058 if (seen_field != null) continue;
1105711059 it.next_idx = @intCast(field_i + 1);
1105811060 return try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
......@@ -11061,7 +11063,7 @@ const ValidatedSwitchBlock = struct {
1106111063 },
1106211064 .error_set => {
1106311065 for (it.error_names.get(ip)[it.next_idx..], it.next_idx..) |err_name, name_i| {
11064 if (it.seen_errors.contains(err_name)) continue;
11066 if (it.seen.errors.contains(err_name)) continue;
1106511067 it.next_idx = @intCast(name_i + 1);
1106611068 return .fromInterned(try pt.intern(.{ .err = .{
1106711069 .ty = item_ty.toIntern(),
......@@ -11077,14 +11079,14 @@ const ValidatedSwitchBlock = struct {
1107711079 .@"union", .@"struct" => item_ty.backingIntType(zcu),
1107811080 else => unreachable,
1107911081 };
11080 while (it.next_idx < it.seen_ranges.len and
11081 cur_val.eql(it.seen_ranges.items(.first)[it.next_idx], int_ty, zcu))
11082 while (it.next_idx < it.seen.ranges.list.len and
11083 cur_val.eql(it.seen.ranges.list.items(.first)[it.next_idx], int_ty, zcu))
1108211084 {
1108311085 defer it.next_idx += 1;
1108411086 const incr = try arith.incrementDefinedInt(
1108511087 sema,
1108611088 int_ty,
11087 it.seen_ranges.items(.last)[it.next_idx],
11089 it.seen.ranges.list.items(.last)[it.next_idx],
1108811090 );
1108911091 if (incr.overflow) {
1109011092 it.next_val = null;
......@@ -11101,19 +11103,19 @@ const ValidatedSwitchBlock = struct {
1110111103 };
1110211104 },
1110311105 .bool => {
11104 if (!it.seen_true) {
11105 it.seen_true = true;
11106 if (!it.handled_true) {
11107 it.handled_true = true;
1110611108 return .true;
1110711109 }
11108 if (!it.seen_false) {
11109 it.seen_false = true;
11110 if (!it.handled_false) {
11111 it.handled_false = true;
1111011112 return .false;
1111111113 }
1111211114 return null;
1111311115 },
1111411116 .void => {
11115 if (!it.seen_void) {
11116 it.seen_void = true;
11117 if (!it.handled_void) {
11118 it.handled_void = true;
1111711119 return .void;
1111811120 }
1111911121 return null;
......@@ -11186,14 +11188,8 @@ fn validateSwitchBlock(
1118611188 operand_ty.assertHasLayout(zcu);
1118711189 const union_obj = ip.loadUnionType(operand_ty.toIntern());
1118811190 switch (union_obj.tag_usage) {
11189 .tagged => {
11190 break :item_ty .fromInterned(union_obj.enum_tag_type);
11191 },
11192 .none => {
11193 if (union_obj.layout == .@"packed") {
11194 break :item_ty operand_ty;
11195 }
11196 },
11191 .tagged => break :item_ty .fromInterned(union_obj.enum_tag_type),
11192 .none => if (union_obj.layout == .@"packed") break :item_ty operand_ty,
1119711193 .safety => {},
1119811194 }
1119911195 return sema.failWithOwnedErrorMsg(block, msg: {
......@@ -11208,27 +11204,47 @@ fn validateSwitchBlock(
1120811204
1120911205 .@"struct" => {
1121011206 operand_ty.assertHasLayout(zcu);
11211 const layout = operand_ty.containerLayout(zcu);
11212 if (layout == .@"packed") {
11213 break :item_ty operand_ty;
11214 }
11207 if (operand_ty.containerLayout(zcu) == .@"packed") break :item_ty operand_ty;
1121511208 return sema.failWithOwnedErrorMsg(block, msg: {
11216 const msg = try sema.errMsg(operand_src, "switch on struct with {t} layout", .{layout});
11209 const msg = try sema.errMsg(operand_src, "switch on non-packed struct", .{});
1121711210 errdefer msg.destroy(sema.gpa);
11218 if (operand_ty.srcLocOrNull(zcu)) |struct_src| {
11219 try sema.errNote(struct_src, msg, "consider 'packed struct' here", .{});
11220 }
11211 try sema.addDeclaredHereNote(msg, operand_ty);
1122111212 break :msg msg;
1122211213 });
1122311214 },
1122411215
11225 .pointer => {
11226 if (!operand_ty.isSlice(zcu)) {
11227 break :item_ty operand_ty;
11228 }
11229 },
11216 .pointer => if (!operand_ty.isSlice(zcu)) break :item_ty operand_ty,
1123011217
11231 else => {},
11218 .optional => return sema.failWithOwnedErrorMsg(block, msg: {
11219 const msg = try sema.errMsg(operand_src, "switch on optional type '{f}'", .{
11220 operand_ty.fmt(pt),
11221 });
11222 errdefer msg.destroy(gpa);
11223 try sema.errNote(operand_src, msg, "consider using '.?', 'orelse', or 'if'", .{});
11224 break :msg msg;
11225 }),
11226
11227 .error_union => return sema.failWithOwnedErrorMsg(block, msg: {
11228 const msg = try sema.errMsg(operand_src, "switch on error union type '{f}'", .{
11229 operand_ty.fmt(pt),
11230 });
11231 errdefer msg.destroy(gpa);
11232 try sema.errNote(operand_src, msg, "consider using 'try', 'catch', or 'if'", .{});
11233 break :msg msg;
11234 }),
11235
11236 .noreturn,
11237 .float,
11238 .comptime_float,
11239 .array,
11240 .vector,
11241 .undefined,
11242 .null,
11243 .@"opaque",
11244 .frame,
11245 .@"anyframe",
11246 .spirv,
11247 => {},
1123211248 }
1123311249 return sema.fail(block, operand_src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
1123411250 };
......@@ -11253,13 +11269,15 @@ fn validateSwitchBlock(
1125311269 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
1125411270
1125511271 // Duplicate checking variables later also used for `inline else`.
11256 var seen_enum_fields: []?LazySrcLoc = &.{};
11257 var seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc) = .empty;
11258 var seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc) = .empty;
11259 var range_set: RangeSet = .empty;
11260 var true_src: ?LazySrcLoc = null;
11261 var false_src: ?LazySrcLoc = null;
11262 var void_src: ?LazySrcLoc = null;
11272 var seen: ValidatedSwitchBlock.Seen = .{
11273 .enum_fields = &.{},
11274 .errors = .empty,
11275 .sparse_values = .empty,
11276 .ranges = .empty,
11277 .true_src = null,
11278 .false_src = null,
11279 .void_src = null,
11280 };
1126311281
1126411282 var else_err_ty: ?Type = null;
1126511283
......@@ -11267,20 +11285,20 @@ fn validateSwitchBlock(
1126711285
1126811286 switch (item_ty.zigTypeTag(zcu)) {
1126911287 .@"enum" => {
11270 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11271 @memset(seen_enum_fields, null);
11272 // `range_set` is used for non-exhaustive enum values that do not
11288 seen.enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11289 @memset(seen.enum_fields, null);
11290 // `seen.ranges` is used for non-exhaustive enum values that do not
1127311291 // correspond to any tags. Since this is rare, we only allocate on
1127411292 // demand in `validateSwitchItem`.
1127511293 },
1127611294 .error_set => {
11277 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11295 try seen.errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1127811296 },
1127911297 .int, .comptime_int, .@"union", .@"struct" => {
11280 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11298 try seen.ranges.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1128111299 },
1128211300 .enum_literal, .@"fn", .pointer, .type => {
11283 try seen_sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11301 try seen.sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1128411302 },
1128511303 .bool, .void => {},
1128611304
......@@ -11323,7 +11341,7 @@ fn validateSwitchBlock(
1132311341 case_vals.appendAssumeCapacity(.none);
1132411342 } else {
1132511343 const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach);
11326 try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
11344 try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, &seen);
1132711345 case_vals.appendAssumeCapacity(item.ref);
1132811346 }
1132911347 }
......@@ -11338,7 +11356,7 @@ fn validateSwitchBlock(
1133811356 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });
1133911357 const first_item, extra_index = try sema.resolveSwitchItem(block, first_src, item_ty, range_info[0], extra_index, switch_inst, prong_info.is_comptime_unreach);
1134011358 const last_item, extra_index = try sema.resolveSwitchItem(block, last_src, item_ty, range_info[1], extra_index, switch_inst, prong_info.is_comptime_unreach);
11341 try sema.validateSwitchItemOrRange(block, range_src, first_item.val, last_item.val, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
11359 try sema.validateSwitchItemOrRange(block, range_src, first_item.val, last_item.val, item_ty, &seen);
1134211360 case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });
1134311361 }
1134411362 }
......@@ -11369,13 +11387,13 @@ fn validateSwitchBlock(
1136911387 // Validate for missing special prongs.
1137011388 switch (item_ty.zigTypeTag(zcu)) {
1137111389 .@"enum" => {
11372 const all_tags_handled = for (seen_enum_fields) |seen_src| {
11390 const all_tags_handled = for (seen.enum_fields) |seen_src| {
1137311391 if (seen_src == null) break false;
1137411392 } else true;
1137511393
1137611394 if (has_else) {
1137711395 if (all_tags_handled) {
11378 if (item_ty.isNonexhaustiveEnum(zcu)) {
11396 if (operand_ty.isNonexhaustiveEnum(zcu)) {
1137911397 if (has_under) return sema.fail(
1138011398 block,
1138111399 else_prong_src,
......@@ -11397,7 +11415,7 @@ fn validateSwitchBlock(
1139711415 .{},
1139811416 );
1139911417 errdefer msg.destroy(sema.gpa);
11400 for (seen_enum_fields, 0..) |seen_src, i| {
11418 for (seen.enum_fields, 0..) |seen_src, i| {
1140111419 if (seen_src != null) continue;
1140211420
1140311421 const field_name = item_ty.enumFieldName(i, zcu);
......@@ -11449,7 +11467,7 @@ fn validateSwitchBlock(
1144911467
1145011468 var seen_errors_from_set: u32 = 0;
1145111469 for (error_names.get(ip)) |error_name| {
11452 if (seen_errors.contains(error_name)) {
11470 if (seen.errors.contains(error_name)) {
1145311471 seen_errors_from_set += 1;
1145411472 } else if (!has_else) {
1145511473 const msg = maybe_msg orelse blk: {
......@@ -11491,7 +11509,7 @@ fn validateSwitchBlock(
1149111509 var names: InferredErrorSet.NameMap = .{};
1149211510 try names.ensureUnusedCapacity(sema.arena, error_names.len);
1149311511 for (error_names.get(ip)) |error_name| {
11494 if (seen_errors.contains(error_name)) continue;
11512 if (seen.errors.contains(error_name)) continue;
1149511513 names.putAssumeCapacityNoClobber(error_name, {});
1149611514 }
1149711515 // No need to keep the hash map metadata correct; here we
......@@ -11509,7 +11527,7 @@ fn validateSwitchBlock(
1150911527 };
1151011528 const min_int = try int_ty.minInt(pt, int_ty);
1151111529 const max_int = try int_ty.maxInt(pt, int_ty);
11512 if (try range_set.spans(arena, min_int, max_int, int_ty, zcu)) {
11530 if (try seen.ranges.spans(arena, min_int, max_int, int_ty, zcu)) {
1151311531 if (has_else) {
1151411532 return sema.fail(
1151511533 block,
......@@ -11542,8 +11560,8 @@ fn validateSwitchBlock(
1154211560 },
1154311561 .bool, .void => |type_tag| {
1154411562 const all_values_handled = switch (type_tag) {
11545 .bool => true_src != null and false_src != null,
11546 .void => void_src != null,
11563 .bool => seen.true_src != null and seen.false_src != null,
11564 .void => seen.void_src != null,
1154711565 else => unreachable,
1154811566 };
1154911567 if (has_else) {
......@@ -11570,13 +11588,7 @@ fn validateSwitchBlock(
1157011588 }
1157111589
1157211590 return .{
11573 .seen_enum_fields = seen_enum_fields,
11574 .seen_errors = seen_errors,
11575 .seen_ranges = range_set.ranges.slice(),
11576 .true_src = true_src,
11577 .false_src = false_src,
11578 .void_src = void_src,
11579
11591 .seen = seen,
1158011592 .case_vals = case_vals.items,
1158111593 .else_case = else_case,
1158211594 .else_err_ty = else_err_ty,
......@@ -11754,7 +11766,7 @@ fn resolveSwitchBlock(
1175411766 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
1175511767 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
1175611768 if (tagged_union_originally) {
11757 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
11769 for (validated_switch.seen.enum_fields, 0..) |maybe_seen, field_i| {
1175811770 if (maybe_seen != null) continue;
1175911771 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
1176011772 } else {
......@@ -12559,13 +12571,7 @@ fn validateSwitchItemOrRange(
1255912571 item_val: Value,
1256012572 opt_last_val: ?Value,
1256112573 item_ty: Type,
12562 seen_enum_fields: []?LazySrcLoc,
12563 seen_errors: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
12564 seen_sparse_values: *std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
12565 range_set: *RangeSet,
12566 true_src: *?LazySrcLoc,
12567 false_src: *?LazySrcLoc,
12568 void_src: *?LazySrcLoc,
12574 seen: *ValidatedSwitchBlock.Seen,
1256912575) CompileError!void {
1257012576 const pt = sema.pt;
1257112577 const zcu = pt.zcu;
......@@ -12574,88 +12580,117 @@ fn validateSwitchItemOrRange(
1257412580 .@"enum" => {
1257512581 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
1257612582 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
12577 const maybe_prev_src = seen_enum_fields[field_index];
12578 seen_enum_fields[field_index] = item_src;
12583 const maybe_prev_src = seen.enum_fields[field_index];
12584 seen.enum_fields[field_index] = item_src;
1257912585 break :maybe_prev_src maybe_prev_src;
1258012586 } else {
12581 break :maybe_prev_src try range_set.add(sema.arena, .{
12587 try seen.ranges.ensureUnusedCapacity(sema.arena, 1);
12588 break :maybe_prev_src if (seen.ranges.addAssumeCapacity(.{
1258212589 .first = .fromInterned(int),
1258312590 .last = .fromInterned(int),
1258412591 .src = item_src,
12585 }, .fromInterned(ip.typeOf(int)), zcu);
12592 }, .fromInterned(ip.typeOf(int)), zcu)) |prev| prev.src else null;
1258612593 }
1258712594 },
1258812595 .error_set => {
1258912596 const error_name = ip.indexToKey(item_val.toIntern()).err.name;
12590 break :maybe_prev_src if (seen_errors.fetchPutAssumeCapacity(error_name, item_src)) |prev|
12597 break :maybe_prev_src if (seen.errors.fetchPutAssumeCapacity(error_name, item_src)) |prev|
1259112598 prev.value
1259212599 else
1259312600 null;
1259412601 },
1259512602 .int, .comptime_int => {
12596 if (opt_last_val) |last_val| {
12597 const first_val = item_val;
12603 const first_val = item_val;
12604 const last_val: Value = last_val: {
12605 const last_val = opt_last_val orelse break :last_val item_val;
1259812606 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {
1259912607 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
1260012608 }
12601 break :maybe_prev_src range_set.addAssumeCapacity(.{
12602 .first = first_val,
12603 .last = last_val,
12604 .src = item_src,
12605 }, item_ty, zcu);
12606 } else {
12607 break :maybe_prev_src range_set.addAssumeCapacity(.{
12608 .first = item_val,
12609 .last = item_val,
12610 .src = item_src,
12611 }, item_ty, zcu);
12609 break :last_val last_val;
12610 };
12611 if (seen.ranges.addAssumeCapacity(.{
12612 .first = first_val,
12613 .last = last_val,
12614 .src = item_src,
12615 }, item_ty, zcu)) |prev_range| {
12616 const overlap_start = first_val.numberMax(prev_range.first, zcu);
12617 const overlap_end = last_val.numberMin(prev_range.last, zcu);
12618 if (overlap_start.eql(overlap_end, item_ty, zcu)) {
12619 return sema.failWithOwnedErrorMsg(block, msg: {
12620 const msg = try sema.errMsg(item_src, "duplicate switch value '{f}'", .{
12621 overlap_start.fmtValueSema(pt, sema),
12622 });
12623 errdefer msg.destroy(sema.gpa);
12624 if (prev_range.first.eql(prev_range.last, item_ty, zcu)) {
12625 try sema.errNote(prev_range.src, msg, "previous value here", .{});
12626 } else {
12627 try sema.errNote(prev_range.src, msg, "previous value inside range here", .{});
12628 }
12629 break :msg msg;
12630 });
12631 }
12632 assert(!prev_range.first.eql(prev_range.last, item_ty, zcu));
12633 return sema.failWithOwnedErrorMsg(block, msg: {
12634 const msg = try sema.errMsg(item_src, "duplicate switch ranges", .{});
12635 errdefer msg.destroy(sema.gpa);
12636 if (first_val.eql(prev_range.first, item_ty, zcu) and
12637 last_val.eql(prev_range.last, item_ty, zcu))
12638 {
12639 try sema.errNote(prev_range.src, msg, "previous range here", .{});
12640 } else {
12641 try sema.errNote(prev_range.src, msg, "overlaps with previous range here", .{});
12642 try sema.errNote(prev_range.src, msg, "ranges overlap from '{f}' to '{f}'", .{
12643 overlap_start.fmtValueSema(pt, sema), overlap_end.fmtValueSema(pt, sema),
12644 });
12645 }
12646 break :msg msg;
12647 });
1261212648 }
12649 break :maybe_prev_src null;
1261312650 },
1261412651 .@"union", .@"struct" => {
1261512652 const backing_int_val = ip.indexToKey(item_val.toIntern()).bitpack.backing_int_val;
12616 break :maybe_prev_src range_set.addAssumeCapacity(.{
12653 break :maybe_prev_src if (seen.ranges.addAssumeCapacity(.{
1261712654 .first = .fromInterned(backing_int_val),
1261812655 .last = .fromInterned(backing_int_val),
1261912656 .src = item_src,
12620 }, item_ty.backingIntType(zcu), zcu);
12657 }, item_ty.backingIntType(zcu), zcu)) |prev| prev.src else null;
1262112658 },
1262212659 .enum_literal, .@"fn", .pointer, .type => {
12623 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
12660 break :maybe_prev_src if (seen.sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
1262412661 prev.value
1262512662 else
1262612663 null;
1262712664 },
1262812665 .bool => {
1262912666 if (item_val.toBool()) {
12630 if (true_src.*) |prev_src| break :maybe_prev_src prev_src;
12631 true_src.* = item_src;
12667 if (seen.true_src) |prev_src| break :maybe_prev_src prev_src;
12668 seen.true_src = item_src;
1263212669 } else {
12633 if (false_src.*) |prev_src| break :maybe_prev_src prev_src;
12634 false_src.* = item_src;
12670 if (seen.false_src) |prev_src| break :maybe_prev_src prev_src;
12671 seen.false_src = item_src;
1263512672 }
1263612673 break :maybe_prev_src null;
1263712674 },
1263812675 .void => {
12639 if (void_src.*) |prev_src| break :maybe_prev_src prev_src;
12640 void_src.* = item_src;
12676 if (seen.void_src) |prev_src| break :maybe_prev_src prev_src;
12677 seen.void_src = item_src;
1264112678 break :maybe_prev_src null;
1264212679 },
1264312680 else => unreachable, // should have already checked for invalid types
1264412681 };
1264512682 if (maybe_prev_src) |prev_src| {
1264612683 return sema.failWithOwnedErrorMsg(block, msg: {
12647 const msg = try sema.errMsg(
12648 item_src,
12649 "duplicate switch value",
12650 .{},
12651 );
12684 const msg = try sema.errMsg(item_src, "duplicate switch value '{f}'", .{
12685 item_val.fmtValueSema(pt, sema),
12686 });
1265212687 errdefer msg.destroy(sema.gpa);
12653 try sema.errNote(
12654 prev_src,
12655 msg,
12656 "previous value here",
12657 .{},
12658 );
12688 try sema.errNote(prev_src, msg, "previous value here", .{});
12689 if (item_ty.zigTypeTag(zcu) == .type) {
12690 try sema.addDeclaredHereNote(msg, item_val.toType());
12691 } else {
12692 try sema.addDeclaredHereNote(msg, item_ty);
12693 }
1265912694 break :msg msg;
1266012695 });
1266112696 }
test/cases/compile_errors/capture_by_ref_discard.zig+5
......@@ -16,9 +16,14 @@ export fn d() void {
1616 while (null) |*_| {}
1717}
1818
19export fn e() void {
20 if (0) |*_| {} else |err| switch (err) {}
21}
22
1923// error
2024//
2125// :2:16: error: pointer modifier invalid on discard
2226// :7:18: error: pointer modifier invalid on discard
2327// :12:16: error: pointer modifier invalid on discard
2428// :16:19: error: pointer modifier invalid on discard
29// :20:13: error: pointer modifier invalid on discard
test/cases/compile_errors/duplicate_boolean_switch_value.zig+2-2
......@@ -17,7 +17,7 @@ comptime {
1717
1818// error
1919//
20// :5:9: error: duplicate switch value
20// :5:9: error: duplicate switch value 'true'
2121// :3:9: note: previous value here
22// :13:9: error: duplicate switch value
22// :13:9: error: duplicate switch value 'false'
2323// :11:9: note: previous value here
test/cases/compile_errors/duplicate_error_in_switch.zig+1-1
......@@ -16,5 +16,5 @@ fn foo(x: i32) !void {
1616
1717// error
1818//
19// :5:9: error: duplicate switch value
19// :5:9: error: duplicate switch value 'error.Foo'
2020// :3:9: note: previous value here
test/cases/compile_errors/switch_expression-duplicate_enumeration_prong.zig+2-1
......@@ -20,5 +20,6 @@ export fn entry() usize {
2020
2121// error
2222//
23// :13:15: error: duplicate switch value
23// :13:15: error: duplicate switch value '.Two'
2424// :10:15: note: previous value here
25// :1:16: note: enum declared here
test/cases/compile_errors/switch_expression-duplicate_enumeration_prong_when_else_present.zig+2-1
......@@ -21,5 +21,6 @@ export fn entry() usize {
2121
2222// error
2323//
24// :13:15: error: duplicate switch value
24// :13:15: error: duplicate switch value '.Two'
2525// :10:15: note: previous value here
26// :1:16: note: enum declared here
test/cases/compile_errors/switch_expression-duplicate_error_prong.zig+2-2
......@@ -25,7 +25,7 @@ export fn entry() usize {
2525
2626// error
2727//
28// :8:9: error: duplicate switch value
28// :8:9: error: duplicate switch value 'error.Foo'
2929// :5:9: note: previous value here
30// :16:9: error: duplicate switch value
30// :16:9: error: duplicate switch value 'error.Foo'
3131// :13:9: note: previous value here
test/cases/compile_errors/switch_expression-duplicate_error_prong_when_else_present.zig+2-2
......@@ -27,7 +27,7 @@ export fn entry() usize {
2727
2828// error
2929//
30// :8:9: error: duplicate switch value
30// :8:9: error: duplicate switch value 'error.Foo'
3131// :5:9: note: previous value here
32// :17:9: error: duplicate switch value
32// :17:9: error: duplicate switch value 'error.Foo'
3333// :14:9: note: previous value here
test/cases/compile_errors/switch_expression-duplicate_or_overlapping_integer_value.zig deleted-16
......@@ -1,16 +0,0 @@
1fn foo(x: u8) u8 {
2 return switch (x) {
3 0...100 => @as(u8, 0),
4 101...200 => 1,
5 201, 203...207 => 2,
6 206...255 => 3,
7 };
8}
9export fn entry() usize {
10 return @sizeOf(@TypeOf(&foo));
11}
12
13// error
14//
15// :6:12: error: duplicate switch value
16// :5:17: note: previous value here
test/cases/compile_errors/switch_expression-duplicate_type.zig+1-1
......@@ -13,5 +13,5 @@ export fn entry() usize {
1313
1414// error
1515//
16// :6:9: error: duplicate switch value
16// :6:9: error: duplicate switch value 'u32'
1717// :4:9: note: previous value here
test/cases/compile_errors/switch_expression-duplicate_type_struct_alias.zig+2-1
......@@ -17,5 +17,6 @@ export fn entry() usize {
1717
1818// error
1919//
20// :10:9: error: duplicate switch value
20// :10:9: error: duplicate switch value 'tmp.Test'
2121// :8:9: note: previous value here
22// :1:14: note: struct declared here
test/cases/compile_errors/switch_on_invalid_type.zig created+103
......@@ -0,0 +1,103 @@
1const AutoUnion = union { a: u8 };
2export fn entry1() void {
3 switch (@as(AutoUnion, .{ .a = 123 })) {
4 else => {},
5 }
6}
7
8const ExternUnion = union { a: u8 };
9export fn entry2() void {
10 switch (@as(ExternUnion, .{ .a = 123 })) {
11 else => {},
12 }
13}
14
15const AutoStruct = struct { a: u8 };
16export fn entry3() void {
17 switch (@as(AutoStruct, .{ .a = 123 })) {
18 else => {},
19 }
20}
21
22const ExternStruct = extern struct { a: u8 };
23export fn entry4() void {
24 switch (@as(ExternStruct, .{ .a = 123 })) {
25 else => {},
26 }
27}
28
29export fn entry5() void {
30 switch (@as([]const u16, &.{ 1, 2, 3 })) {
31 else => {},
32 }
33}
34
35export fn entry6() void {
36 switch (@as([3]u16, .{ 1, 2, 3 })) {
37 else => {},
38 }
39}
40
41export fn entry7() void {
42 switch (@as(@Vector(3, u16), .{ 1, 2, 3 })) {
43 else => {},
44 }
45}
46
47export fn entry8() void {
48 switch (@as(?u16, 123)) {
49 else => {},
50 }
51}
52
53export fn entry9() void {
54 switch (@as(anyerror!u16, 123)) {
55 else => {},
56 }
57}
58
59export fn entry10() void {
60 switch (@as(f32, 123)) {
61 else => {},
62 }
63}
64
65export fn entry11() void {
66 switch (@as(comptime_float, 123)) {
67 else => {},
68 }
69}
70
71export fn entry12() void {
72 switch (undefined) {
73 else => {},
74 }
75}
76
77export fn entry13() void {
78 switch (null) {
79 else => {},
80 }
81}
82
83// error
84//
85// :3:13: error: switch on union with no attached enum
86// :1:19: note: consider 'union(enum)' here
87// :10:13: error: switch on union with no attached enum
88// :8:21: note: consider 'union(enum)' here
89// :17:13: error: switch on non-packed struct
90// :15:20: note: struct declared here
91// :24:13: error: switch on non-packed struct
92// :22:29: note: struct declared here
93// :30:13: error: switch on type '[]const u16'
94// :36:13: error: switch on type '[3]u16'
95// :42:13: error: switch on type '@Vector(3, u16)'
96// :48:13: error: switch on optional type '?u16'
97// :48:13: note: consider using '.?', 'orelse', or 'if'
98// :54:13: error: switch on error union type 'anyerror!u16'
99// :54:13: note: consider using 'try', 'catch', or 'if'
100// :60:13: error: switch on type 'f32'
101// :66:13: error: switch on type 'comptime_float'
102// :72:13: error: switch on type '@TypeOf(undefined)'
103// :78:13: error: switch on type '@TypeOf(null)'
test/cases/compile_errors/switch_on_non_packed_struct.zig deleted-25
......@@ -1,25 +0,0 @@
1const Auto = struct {
2 a: u8,
3};
4export fn entry1(a: u8) void {
5 const s: Auto = .{ .a = a };
6 switch (s) {
7 else => {},
8 }
9}
10
11const Extern = extern struct {
12 a: u8,
13};
14export fn entry2(s: Extern) void {
15 switch (s) {
16 else => {},
17 }
18}
19
20// error
21//
22// :6:13: error: switch on struct with auto layout
23// :1:14: note: consider 'packed struct' here
24// :15:13: error: switch on struct with extern layout
25// :11:23: note: consider 'packed struct' here
test/cases/compile_errors/switch_on_union_with_nonexhaustive_tag_is_exhaustive.zig created+56
......@@ -0,0 +1,56 @@
1const E = enum(u8) {
2 a,
3 b,
4 _,
5};
6const U = union(E) {
7 a,
8 b,
9};
10fn foo() U {
11 return undefined;
12}
13
14export fn entry1() void {
15 const u = foo();
16 switch (u) {
17 .a => {},
18 }
19}
20export fn entry2() void {
21 const u = foo();
22 switch (u) {
23 .a => {},
24 .b => {},
25 else => {},
26 }
27}
28export fn entry3() void {
29 const u = foo();
30 switch (u) {
31 .a => {},
32 .b => {},
33 _ => {},
34 }
35}
36export fn entry4() void {
37 const u = foo();
38 switch (u) {
39 .a => {},
40 else => {},
41 _ => {},
42 }
43}
44
45// error
46//
47// :16:5: error: switch must handle all possibilities
48// :3:5: note: unhandled enumeration value: 'b'
49// :1:11: note: enum 'tmp.E' declared here
50// :25:14: error: unreachable else prong; all cases already handled
51// :30:5: error: '_' prong only allowed when switching on non-exhaustive enums
52// :33:9: note: '_' prong here
53// :30:5: note: consider using 'else'
54// :38:5: error: '_' prong only allowed when switching on non-exhaustive enums
55// :41:9: note: '_' prong here
56// :38:5: note: consider using 'else'
test/cases/compile_errors/switch_with_overlapping_case_ranges.zig+36-6
......@@ -28,13 +28,43 @@ export fn entry4(x: u8) void {
2828 }
2929}
3030
31export fn entry5(x: u8) void {
32 switch (x) {
33 0...255 => {},
34 4...120 => {},
35 }
36}
37
38export fn entry6(x: u8) void {
39 switch (x) {
40 0...130 => {},
41 120...255 => {},
42 }
43}
44
45export fn entry7(x: u8) void {
46 switch (x) {
47 2 => {},
48 0...255 => {},
49 }
50}
51
3152// error
3253//
33// :4:10: error: duplicate switch value
34// :3:10: note: previous value here
35// :11:10: error: duplicate switch value
36// :10:13: note: previous value here
37// :17:10: error: duplicate switch value
54// :4:10: error: duplicate switch ranges
55// :3:10: note: overlaps with previous range here
56// :3:10: note: ranges overlap from '1' to '2'
57// :11:10: error: duplicate switch value '5'
58// :10:13: note: previous value inside range here
59// :17:10: error: duplicate switch value '5'
3860// :18:9: note: previous value here
39// :27:10: error: duplicate switch value
61// :27:10: error: duplicate switch value '6'
4062// :26:9: note: previous value here
63// :34:10: error: duplicate switch ranges
64// :33:10: note: overlaps with previous range here
65// :33:10: note: ranges overlap from '4' to '120'
66// :41:12: error: duplicate switch ranges
67// :40:10: note: overlaps with previous range here
68// :40:10: note: ranges overlap from '120' to '130'
69// :48:10: error: duplicate switch value '2'
70// :47:9: note: previous value here