authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-07-24 14:34:00+02:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-07-24 17:48:10+02:00
logc1682a01c1c3994b3a30173197954ba65992bee1
treec37b1b46533fc05da67f1602d07d447d1031b35b
parente48779fe1f6127d50008277fb8662566769975da

Sema: improve switch duplicate item/range errors

Now reports which values are duplicated and the overlap of duplicate ranges.

12 files changed, 200 insertions(+), 167 deletions(-)

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+132-111
......@@ -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;
......@@ -11267,13 +11269,15 @@ fn validateSwitchBlock(
1126711269 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
1126811270
1126911271 // Duplicate checking variables later also used for `inline else`.
11270 var seen_enum_fields: []?LazySrcLoc = &.{};
11271 var seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc) = .empty;
11272 var seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc) = .empty;
11273 var range_set: RangeSet = .empty;
11274 var true_src: ?LazySrcLoc = null;
11275 var false_src: ?LazySrcLoc = null;
11276 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 };
1127711281
1127811282 var else_err_ty: ?Type = null;
1127911283
......@@ -11281,20 +11285,20 @@ fn validateSwitchBlock(
1128111285
1128211286 switch (item_ty.zigTypeTag(zcu)) {
1128311287 .@"enum" => {
11284 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11285 @memset(seen_enum_fields, null);
11286 // `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
1128711291 // correspond to any tags. Since this is rare, we only allocate on
1128811292 // demand in `validateSwitchItem`.
1128911293 },
1129011294 .error_set => {
11291 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11295 try seen.errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1129211296 },
1129311297 .int, .comptime_int, .@"union", .@"struct" => {
11294 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11298 try seen.ranges.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1129511299 },
1129611300 .enum_literal, .@"fn", .pointer, .type => {
11297 try seen_sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11301 try seen.sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
1129811302 },
1129911303 .bool, .void => {},
1130011304
......@@ -11337,7 +11341,7 @@ fn validateSwitchBlock(
1133711341 case_vals.appendAssumeCapacity(.none);
1133811342 } else {
1133911343 const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach);
11340 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);
1134111345 case_vals.appendAssumeCapacity(item.ref);
1134211346 }
1134311347 }
......@@ -11352,7 +11356,7 @@ fn validateSwitchBlock(
1135211356 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });
1135311357 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);
1135411358 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);
11355 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);
1135611360 case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });
1135711361 }
1135811362 }
......@@ -11383,7 +11387,7 @@ fn validateSwitchBlock(
1138311387 // Validate for missing special prongs.
1138411388 switch (item_ty.zigTypeTag(zcu)) {
1138511389 .@"enum" => {
11386 const all_tags_handled = for (seen_enum_fields) |seen_src| {
11390 const all_tags_handled = for (seen.enum_fields) |seen_src| {
1138711391 if (seen_src == null) break false;
1138811392 } else true;
1138911393
......@@ -11411,7 +11415,7 @@ fn validateSwitchBlock(
1141111415 .{},
1141211416 );
1141311417 errdefer msg.destroy(sema.gpa);
11414 for (seen_enum_fields, 0..) |seen_src, i| {
11418 for (seen.enum_fields, 0..) |seen_src, i| {
1141511419 if (seen_src != null) continue;
1141611420
1141711421 const field_name = item_ty.enumFieldName(i, zcu);
......@@ -11463,7 +11467,7 @@ fn validateSwitchBlock(
1146311467
1146411468 var seen_errors_from_set: u32 = 0;
1146511469 for (error_names.get(ip)) |error_name| {
11466 if (seen_errors.contains(error_name)) {
11470 if (seen.errors.contains(error_name)) {
1146711471 seen_errors_from_set += 1;
1146811472 } else if (!has_else) {
1146911473 const msg = maybe_msg orelse blk: {
......@@ -11505,7 +11509,7 @@ fn validateSwitchBlock(
1150511509 var names: InferredErrorSet.NameMap = .{};
1150611510 try names.ensureUnusedCapacity(sema.arena, error_names.len);
1150711511 for (error_names.get(ip)) |error_name| {
11508 if (seen_errors.contains(error_name)) continue;
11512 if (seen.errors.contains(error_name)) continue;
1150911513 names.putAssumeCapacityNoClobber(error_name, {});
1151011514 }
1151111515 // No need to keep the hash map metadata correct; here we
......@@ -11523,7 +11527,7 @@ fn validateSwitchBlock(
1152311527 };
1152411528 const min_int = try int_ty.minInt(pt, int_ty);
1152511529 const max_int = try int_ty.maxInt(pt, int_ty);
11526 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)) {
1152711531 if (has_else) {
1152811532 return sema.fail(
1152911533 block,
......@@ -11556,8 +11560,8 @@ fn validateSwitchBlock(
1155611560 },
1155711561 .bool, .void => |type_tag| {
1155811562 const all_values_handled = switch (type_tag) {
11559 .bool => true_src != null and false_src != null,
11560 .void => void_src != null,
11563 .bool => seen.true_src != null and seen.false_src != null,
11564 .void => seen.void_src != null,
1156111565 else => unreachable,
1156211566 };
1156311567 if (has_else) {
......@@ -11584,13 +11588,7 @@ fn validateSwitchBlock(
1158411588 }
1158511589
1158611590 return .{
11587 .seen_enum_fields = seen_enum_fields,
11588 .seen_errors = seen_errors,
11589 .seen_ranges = range_set.ranges.slice(),
11590 .true_src = true_src,
11591 .false_src = false_src,
11592 .void_src = void_src,
11593
11591 .seen = seen,
1159411592 .case_vals = case_vals.items,
1159511593 .else_case = else_case,
1159611594 .else_err_ty = else_err_ty,
......@@ -11768,7 +11766,7 @@ fn resolveSwitchBlock(
1176811766 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
1176911767 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
1177011768 if (tagged_union_originally) {
11771 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
11769 for (validated_switch.seen.enum_fields, 0..) |maybe_seen, field_i| {
1177211770 if (maybe_seen != null) continue;
1177311771 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
1177411772 } else {
......@@ -12573,13 +12571,7 @@ fn validateSwitchItemOrRange(
1257312571 item_val: Value,
1257412572 opt_last_val: ?Value,
1257512573 item_ty: Type,
12576 seen_enum_fields: []?LazySrcLoc,
12577 seen_errors: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
12578 seen_sparse_values: *std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
12579 range_set: *RangeSet,
12580 true_src: *?LazySrcLoc,
12581 false_src: *?LazySrcLoc,
12582 void_src: *?LazySrcLoc,
12574 seen: *ValidatedSwitchBlock.Seen,
1258312575) CompileError!void {
1258412576 const pt = sema.pt;
1258512577 const zcu = pt.zcu;
......@@ -12588,88 +12580,117 @@ fn validateSwitchItemOrRange(
1258812580 .@"enum" => {
1258912581 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
1259012582 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
12591 const maybe_prev_src = seen_enum_fields[field_index];
12592 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;
1259312585 break :maybe_prev_src maybe_prev_src;
1259412586 } else {
12595 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(.{
1259612589 .first = .fromInterned(int),
1259712590 .last = .fromInterned(int),
1259812591 .src = item_src,
12599 }, .fromInterned(ip.typeOf(int)), zcu);
12592 }, .fromInterned(ip.typeOf(int)), zcu)) |prev| prev.src else null;
1260012593 }
1260112594 },
1260212595 .error_set => {
1260312596 const error_name = ip.indexToKey(item_val.toIntern()).err.name;
12604 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|
1260512598 prev.value
1260612599 else
1260712600 null;
1260812601 },
1260912602 .int, .comptime_int => {
12610 if (opt_last_val) |last_val| {
12611 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;
1261212606 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {
1261312607 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
1261412608 }
12615 break :maybe_prev_src range_set.addAssumeCapacity(.{
12616 .first = first_val,
12617 .last = last_val,
12618 .src = item_src,
12619 }, item_ty, zcu);
12620 } else {
12621 break :maybe_prev_src range_set.addAssumeCapacity(.{
12622 .first = item_val,
12623 .last = item_val,
12624 .src = item_src,
12625 }, 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 });
1262612648 }
12649 break :maybe_prev_src null;
1262712650 },
1262812651 .@"union", .@"struct" => {
1262912652 const backing_int_val = ip.indexToKey(item_val.toIntern()).bitpack.backing_int_val;
12630 break :maybe_prev_src range_set.addAssumeCapacity(.{
12653 break :maybe_prev_src if (seen.ranges.addAssumeCapacity(.{
1263112654 .first = .fromInterned(backing_int_val),
1263212655 .last = .fromInterned(backing_int_val),
1263312656 .src = item_src,
12634 }, item_ty.backingIntType(zcu), zcu);
12657 }, item_ty.backingIntType(zcu), zcu)) |prev| prev.src else null;
1263512658 },
1263612659 .enum_literal, .@"fn", .pointer, .type => {
12637 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|
1263812661 prev.value
1263912662 else
1264012663 null;
1264112664 },
1264212665 .bool => {
1264312666 if (item_val.toBool()) {
12644 if (true_src.*) |prev_src| break :maybe_prev_src prev_src;
12645 true_src.* = item_src;
12667 if (seen.true_src) |prev_src| break :maybe_prev_src prev_src;
12668 seen.true_src = item_src;
1264612669 } else {
12647 if (false_src.*) |prev_src| break :maybe_prev_src prev_src;
12648 false_src.* = item_src;
12670 if (seen.false_src) |prev_src| break :maybe_prev_src prev_src;
12671 seen.false_src = item_src;
1264912672 }
1265012673 break :maybe_prev_src null;
1265112674 },
1265212675 .void => {
12653 if (void_src.*) |prev_src| break :maybe_prev_src prev_src;
12654 void_src.* = item_src;
12676 if (seen.void_src) |prev_src| break :maybe_prev_src prev_src;
12677 seen.void_src = item_src;
1265512678 break :maybe_prev_src null;
1265612679 },
1265712680 else => unreachable, // should have already checked for invalid types
1265812681 };
1265912682 if (maybe_prev_src) |prev_src| {
1266012683 return sema.failWithOwnedErrorMsg(block, msg: {
12661 const msg = try sema.errMsg(
12662 item_src,
12663 "duplicate switch value",
12664 .{},
12665 );
12684 const msg = try sema.errMsg(item_src, "duplicate switch value '{f}'", .{
12685 item_val.fmtValueSema(pt, sema),
12686 });
1266612687 errdefer msg.destroy(sema.gpa);
12667 try sema.errNote(
12668 prev_src,
12669 msg,
12670 "previous value here",
12671 .{},
12672 );
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 }
1267312694 break :msg msg;
1267412695 });
1267512696 }
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_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