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 @@...@@ -1,6 +1,6 @@
1const RangeSet = @This();1const RangeSet = @This();
22
3ranges: std.MultiArrayList(Range),3list: std.MultiArrayList(Range),
44
5pub const Range = struct {5pub const Range = struct {
6 first: Value,6 first: Value,
...@@ -8,41 +8,36 @@ pub const Range = struct {...@@ -8,41 +8,36 @@ pub const Range = struct {
8 src: LazySrcLoc,8 src: LazySrcLoc,
9};9};
1010
11pub const empty: RangeSet = .{ .ranges = .empty };11pub const empty: RangeSet = .{ .list = .empty };
1212
13pub fn deinit(self: *RangeSet, allocator: Allocator) void {13pub fn deinit(self: *RangeSet, allocator: Allocator) void {
14 self.ranges.deinit(allocator);14 self.list.deinit(allocator);
15 self.* = undefined;15 self.* = undefined;
16}16}
1717
18pub fn ensureUnusedCapacity(self: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {18pub fn ensureUnusedCapacity(set: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {
19 return self.ranges.ensureUnusedCapacity(allocator, additional_count);19 return set.list.ensureUnusedCapacity(allocator, additional_count);
20}20}
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 {
23 assert(new.first.typeOf(zcu).eql(ty));23 assert(new.first.typeOf(zcu).eql(ty));
24 assert(new.last.typeOf(zcu).eql(ty));24 assert(new.last.typeOf(zcu).eql(ty));
25 assert(new.first.compareScalar(.lte, new.last, ty, zcu));25 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, .{
28 .val = new.first,28 .val = new.first,
29 .zcu = zcu,29 .zcu = zcu,
30 }), compare);30 }), compare);
3131
32 if (idx != set.ranges.len and // `new.first` is *not* greater than all `old.last`32 if (idx != set.list.len and // `new.first` is *not* greater than all `old.last`
33 new.last.compareScalar(.gte, set.ranges.items(.first)[idx], ty, zcu))33 new.last.compareScalar(.gte, set.list.items(.first)[idx], ty, zcu))
34 {34 {
35 return set.ranges.items(.src)[idx]; // `new` overlaps with existing range.35 return set.list.get(idx); // `new` overlaps with existing range.
36 }36 }
37 set.ranges.insertAssumeCapacity(idx, new);37 set.list.insertAssumeCapacity(idx, new);
38 return null;38 return null;
39}39}
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
46pub fn spans(41pub fn spans(
47 set: *RangeSet,42 set: *RangeSet,
48 allocator: Allocator,43 allocator: Allocator,
...@@ -53,13 +48,13 @@ pub fn spans(...@@ -53,13 +48,13 @@ pub fn spans(
53) Allocator.Error!bool {48) Allocator.Error!bool {
54 assert(first.typeOf(zcu).eql(ty));49 assert(first.typeOf(zcu).eql(ty));
55 assert(last.typeOf(zcu).eql(ty));50 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));53 assert(std.sort.isSorted(Value, set.list.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));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) or56 if (!set.list.items(.first)[0].eql(first, ty, zcu) or
62 !set.ranges.items(.last)[set.ranges.len - 1].eql(last, ty, zcu))57 !set.list.items(.last)[set.list.len - 1].eql(last, ty, zcu))
63 {58 {
64 return false;59 return false;
65 }60 }
...@@ -75,8 +70,8 @@ pub fn spans(...@@ -75,8 +70,8 @@ pub fn spans(
7570
76 // look for gaps71 // look for gaps
77 for (72 for (
78 set.ranges.items(.first)[1..],73 set.list.items(.first)[1..],
79 set.ranges.items(.last)[0 .. set.ranges.len - 1],74 set.list.items(.last)[0 .. set.list.len - 1],
80 ) |cur_first, prev_last| {75 ) |cur_first, prev_last| {
81 // prev_last + 1 == cur_first76 // prev_last + 1 == cur_first
82 counter.copy(prev_last.toBigInt(&space, zcu));77 counter.copy(prev_last.toBigInt(&space, zcu));
src/Sema.zig+132-111
...@@ -10760,7 +10760,7 @@ fn finishSwitchBr(...@@ -10760,7 +10760,7 @@ fn finishSwitchBr(
10760 .@"enum" => if (else_is_named_only or10760 .@"enum" => if (else_is_named_only or
10761 !item_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally)10761 !item_ty.isNonexhaustiveEnum(zcu) or tagged_union_originally)
10762 {10762 {
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));
10764 break :check_enumerable .{ undefined, undefined };10764 break :check_enumerable .{ undefined, undefined };
10765 },10765 },
10766 .error_set => if (!operand_ty.isAnyError(zcu)) {10766 .error_set => if (!operand_ty.isAnyError(zcu)) {
...@@ -10881,13 +10881,13 @@ fn finishSwitchBr(...@@ -10881,13 +10881,13 @@ fn finishSwitchBr(
10881 try branch_hints.append(gpa, prong_hint);10881 try branch_hints.append(gpa, prong_hint);
1088210882
10883 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +10883 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 _
10885 case_block.instructions.items.len);10885 case_block.instructions.items.len);
10886 const extra_case = cases_extra.addManyAsArrayAssumeCapacity(10886 const extra_case = cases_extra.addManyAsArrayAssumeCapacity(
10887 @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len,10887 @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len,
10888 );10888 );
10889 var items_len: u32 = 0;10889 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| {
10891 if (seen_field != null) continue;10891 if (seen_field != null) continue;
10892 const item_val = try pt.enumValueFieldIndex(item_ty, @intCast(field_i));10892 const item_val = try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
10893 const item_ref: Air.Inst.Ref = .fromValue(item_val);10893 const item_ref: Air.Inst.Ref = .fromValue(item_val);
...@@ -10920,7 +10920,7 @@ fn finishSwitchBr(...@@ -10920,7 +10920,7 @@ fn finishSwitchBr(
10920 }10920 }
10921 if (tagged_union_originally) {10921 if (tagged_union_originally) {
10922 const union_obj = zcu.typeToUnion(operand_ty).?;10922 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| {
10924 if (seen_field != null) continue;10924 if (seen_field != null) continue;
10925 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_i]);10925 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_i]);
10926 if (!field_ty.isNoReturn(zcu)) break :analyze_body true;10926 if (!field_ty.isNoReturn(zcu)) break :analyze_body true;
...@@ -11004,17 +11004,21 @@ fn finishSwitchBr(...@@ -11004,17 +11004,21 @@ fn finishSwitchBr(
11004}11004}
1100511005
11006const ValidatedSwitchBlock = struct {11006const ValidatedSwitchBlock = struct {
11007 seen_enum_fields: []const ?LazySrcLoc,11007 seen: Seen,
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
11014 case_vals: []const Air.Inst.Ref,11008 case_vals: []const Air.Inst.Ref,
11015 else_case: Zir.UnwrappedSwitchBlock.Case.Else,11009 else_case: Zir.UnwrappedSwitchBlock.Case.Else,
11016 else_err_ty: ?Type,11010 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
11018 fn iterateUnhandledItems(11022 fn iterateUnhandledItems(
11019 validated_switch: *const ValidatedSwitchBlock,11023 validated_switch: *const ValidatedSwitchBlock,
11020 /// May be `undefined` if `item_ty` isn't an `error_set`.11024 /// May be `undefined` if `item_ty` isn't an `error_set`.
...@@ -11023,28 +11027,26 @@ const ValidatedSwitchBlock = struct {...@@ -11023,28 +11027,26 @@ const ValidatedSwitchBlock = struct {
11023 min_int: Value,11027 min_int: Value,
11024 ) UnhandledIterator {11028 ) UnhandledIterator {
11025 return .{11029 return .{
11030 .error_names = error_names,
11031 .seen = &validated_switch.seen,
11032
11026 .next_idx = 0,11033 .next_idx = 0,
11027 .next_val = min_int,11034 .next_val = min_int,
11028 .error_names = error_names,11035 .handled_true = validated_switch.seen.true_src != null,
11029 .seen_enum_fields = validated_switch.seen_enum_fields,11036 .handled_false = validated_switch.seen.false_src != null,
11030 .seen_errors = &validated_switch.seen_errors,11037 .handled_void = validated_switch.seen.void_src != null,
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 };11038 };
11036 }11039 }
1103711040
11038 const UnhandledIterator = struct {11041 const UnhandledIterator = struct {
11042 error_names: InternPool.NullTerminatedString.Slice,
11043 seen: *const Seen,
11044
11039 next_idx: u32,11045 next_idx: u32,
11040 next_val: ?Value,11046 next_val: ?Value,
11041 error_names: InternPool.NullTerminatedString.Slice,11047 handled_true: bool,
11042 seen_enum_fields: []const ?LazySrcLoc,11048 handled_false: bool,
11043 seen_errors: *const std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),11049 handled_void: bool,
11044 seen_ranges: std.MultiArrayList(RangeSet.Range).Slice,
11045 seen_true: bool,
11046 seen_false: bool,
11047 seen_void: bool,
1104811050
11049 fn next(it: *UnhandledIterator, sema: *Sema, item_ty: Type) CompileError!?Value {11051 fn next(it: *UnhandledIterator, sema: *Sema, item_ty: Type) CompileError!?Value {
11050 const pt = sema.pt;11052 const pt = sema.pt;
...@@ -11052,7 +11054,7 @@ const ValidatedSwitchBlock = struct {...@@ -11052,7 +11054,7 @@ const ValidatedSwitchBlock = struct {
11052 const ip = &zcu.intern_pool;11054 const ip = &zcu.intern_pool;
11053 switch (item_ty.zigTypeTag(zcu)) {11055 switch (item_ty.zigTypeTag(zcu)) {
11054 .@"enum" => {11056 .@"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| {
11056 if (seen_field != null) continue;11058 if (seen_field != null) continue;
11057 it.next_idx = @intCast(field_i + 1);11059 it.next_idx = @intCast(field_i + 1);
11058 return try pt.enumValueFieldIndex(item_ty, @intCast(field_i));11060 return try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
...@@ -11061,7 +11063,7 @@ const ValidatedSwitchBlock = struct {...@@ -11061,7 +11063,7 @@ const ValidatedSwitchBlock = struct {
11061 },11063 },
11062 .error_set => {11064 .error_set => {
11063 for (it.error_names.get(ip)[it.next_idx..], it.next_idx..) |err_name, name_i| {11065 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;
11065 it.next_idx = @intCast(name_i + 1);11067 it.next_idx = @intCast(name_i + 1);
11066 return .fromInterned(try pt.intern(.{ .err = .{11068 return .fromInterned(try pt.intern(.{ .err = .{
11067 .ty = item_ty.toIntern(),11069 .ty = item_ty.toIntern(),
...@@ -11077,14 +11079,14 @@ const ValidatedSwitchBlock = struct {...@@ -11077,14 +11079,14 @@ const ValidatedSwitchBlock = struct {
11077 .@"union", .@"struct" => item_ty.backingIntType(zcu),11079 .@"union", .@"struct" => item_ty.backingIntType(zcu),
11078 else => unreachable,11080 else => unreachable,
11079 };11081 };
11080 while (it.next_idx < it.seen_ranges.len and11082 while (it.next_idx < it.seen.ranges.list.len and
11081 cur_val.eql(it.seen_ranges.items(.first)[it.next_idx], int_ty, zcu))11083 cur_val.eql(it.seen.ranges.list.items(.first)[it.next_idx], int_ty, zcu))
11082 {11084 {
11083 defer it.next_idx += 1;11085 defer it.next_idx += 1;
11084 const incr = try arith.incrementDefinedInt(11086 const incr = try arith.incrementDefinedInt(
11085 sema,11087 sema,
11086 int_ty,11088 int_ty,
11087 it.seen_ranges.items(.last)[it.next_idx],11089 it.seen.ranges.list.items(.last)[it.next_idx],
11088 );11090 );
11089 if (incr.overflow) {11091 if (incr.overflow) {
11090 it.next_val = null;11092 it.next_val = null;
...@@ -11101,19 +11103,19 @@ const ValidatedSwitchBlock = struct {...@@ -11101,19 +11103,19 @@ const ValidatedSwitchBlock = struct {
11101 };11103 };
11102 },11104 },
11103 .bool => {11105 .bool => {
11104 if (!it.seen_true) {11106 if (!it.handled_true) {
11105 it.seen_true = true;11107 it.handled_true = true;
11106 return .true;11108 return .true;
11107 }11109 }
11108 if (!it.seen_false) {11110 if (!it.handled_false) {
11109 it.seen_false = true;11111 it.handled_false = true;
11110 return .false;11112 return .false;
11111 }11113 }
11112 return null;11114 return null;
11113 },11115 },
11114 .void => {11116 .void => {
11115 if (!it.seen_void) {11117 if (!it.handled_void) {
11116 it.seen_void = true;11118 it.handled_void = true;
11117 return .void;11119 return .void;
11118 }11120 }
11119 return null;11121 return null;
...@@ -11267,13 +11269,15 @@ fn validateSwitchBlock(...@@ -11267,13 +11269,15 @@ fn validateSwitchBlock(
11267 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);11269 var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
1126811270
11269 // Duplicate checking variables later also used for `inline else`.11271 // Duplicate checking variables later also used for `inline else`.
11270 var seen_enum_fields: []?LazySrcLoc = &.{};11272 var seen: ValidatedSwitchBlock.Seen = .{
11271 var seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc) = .empty;11273 .enum_fields = &.{},
11272 var seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc) = .empty;11274 .errors = .empty,
11273 var range_set: RangeSet = .empty;11275 .sparse_values = .empty,
11274 var true_src: ?LazySrcLoc = null;11276 .ranges = .empty,
11275 var false_src: ?LazySrcLoc = null;11277 .true_src = null,
11276 var void_src: ?LazySrcLoc = null;11278 .false_src = null,
11279 .void_src = null,
11280 };
1127711281
11278 var else_err_ty: ?Type = null;11282 var else_err_ty: ?Type = null;
1127911283
...@@ -11281,20 +11285,20 @@ fn validateSwitchBlock(...@@ -11281,20 +11285,20 @@ fn validateSwitchBlock(
1128111285
11282 switch (item_ty.zigTypeTag(zcu)) {11286 switch (item_ty.zigTypeTag(zcu)) {
11283 .@"enum" => {11287 .@"enum" => {
11284 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));11288 seen.enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11285 @memset(seen_enum_fields, null);11289 @memset(seen.enum_fields, null);
11286 // `range_set` is used for non-exhaustive enum values that do not11290 // `seen.ranges` is used for non-exhaustive enum values that do not
11287 // correspond to any tags. Since this is rare, we only allocate on11291 // correspond to any tags. Since this is rare, we only allocate on
11288 // demand in `validateSwitchItem`.11292 // demand in `validateSwitchItem`.
11289 },11293 },
11290 .error_set => {11294 .error_set => {
11291 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());11295 try seen.errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11292 },11296 },
11293 .int, .comptime_int, .@"union", .@"struct" => {11297 .int, .comptime_int, .@"union", .@"struct" => {
11294 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());11298 try seen.ranges.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11295 },11299 },
11296 .enum_literal, .@"fn", .pointer, .type => {11300 .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());
11298 },11302 },
11299 .bool, .void => {},11303 .bool, .void => {},
1130011304
...@@ -11337,7 +11341,7 @@ fn validateSwitchBlock(...@@ -11337,7 +11341,7 @@ fn validateSwitchBlock(
11337 case_vals.appendAssumeCapacity(.none);11341 case_vals.appendAssumeCapacity(.none);
11338 } else {11342 } else {
11339 const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach);11343 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);
11341 case_vals.appendAssumeCapacity(item.ref);11345 case_vals.appendAssumeCapacity(item.ref);
11342 }11346 }
11343 }11347 }
...@@ -11352,7 +11356,7 @@ fn validateSwitchBlock(...@@ -11352,7 +11356,7 @@ fn validateSwitchBlock(
11352 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });11356 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });
11353 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);11357 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);
11354 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);11358 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);
11356 case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });11360 case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });
11357 }11361 }
11358 }11362 }
...@@ -11383,7 +11387,7 @@ fn validateSwitchBlock(...@@ -11383,7 +11387,7 @@ fn validateSwitchBlock(
11383 // Validate for missing special prongs.11387 // Validate for missing special prongs.
11384 switch (item_ty.zigTypeTag(zcu)) {11388 switch (item_ty.zigTypeTag(zcu)) {
11385 .@"enum" => {11389 .@"enum" => {
11386 const all_tags_handled = for (seen_enum_fields) |seen_src| {11390 const all_tags_handled = for (seen.enum_fields) |seen_src| {
11387 if (seen_src == null) break false;11391 if (seen_src == null) break false;
11388 } else true;11392 } else true;
1138911393
...@@ -11411,7 +11415,7 @@ fn validateSwitchBlock(...@@ -11411,7 +11415,7 @@ fn validateSwitchBlock(
11411 .{},11415 .{},
11412 );11416 );
11413 errdefer msg.destroy(sema.gpa);11417 errdefer msg.destroy(sema.gpa);
11414 for (seen_enum_fields, 0..) |seen_src, i| {11418 for (seen.enum_fields, 0..) |seen_src, i| {
11415 if (seen_src != null) continue;11419 if (seen_src != null) continue;
1141611420
11417 const field_name = item_ty.enumFieldName(i, zcu);11421 const field_name = item_ty.enumFieldName(i, zcu);
...@@ -11463,7 +11467,7 @@ fn validateSwitchBlock(...@@ -11463,7 +11467,7 @@ fn validateSwitchBlock(
1146311467
11464 var seen_errors_from_set: u32 = 0;11468 var seen_errors_from_set: u32 = 0;
11465 for (error_names.get(ip)) |error_name| {11469 for (error_names.get(ip)) |error_name| {
11466 if (seen_errors.contains(error_name)) {11470 if (seen.errors.contains(error_name)) {
11467 seen_errors_from_set += 1;11471 seen_errors_from_set += 1;
11468 } else if (!has_else) {11472 } else if (!has_else) {
11469 const msg = maybe_msg orelse blk: {11473 const msg = maybe_msg orelse blk: {
...@@ -11505,7 +11509,7 @@ fn validateSwitchBlock(...@@ -11505,7 +11509,7 @@ fn validateSwitchBlock(
11505 var names: InferredErrorSet.NameMap = .{};11509 var names: InferredErrorSet.NameMap = .{};
11506 try names.ensureUnusedCapacity(sema.arena, error_names.len);11510 try names.ensureUnusedCapacity(sema.arena, error_names.len);
11507 for (error_names.get(ip)) |error_name| {11511 for (error_names.get(ip)) |error_name| {
11508 if (seen_errors.contains(error_name)) continue;11512 if (seen.errors.contains(error_name)) continue;
11509 names.putAssumeCapacityNoClobber(error_name, {});11513 names.putAssumeCapacityNoClobber(error_name, {});
11510 }11514 }
11511 // No need to keep the hash map metadata correct; here we11515 // No need to keep the hash map metadata correct; here we
...@@ -11523,7 +11527,7 @@ fn validateSwitchBlock(...@@ -11523,7 +11527,7 @@ fn validateSwitchBlock(
11523 };11527 };
11524 const min_int = try int_ty.minInt(pt, int_ty);11528 const min_int = try int_ty.minInt(pt, int_ty);
11525 const max_int = try int_ty.maxInt(pt, int_ty);11529 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)) {
11527 if (has_else) {11531 if (has_else) {
11528 return sema.fail(11532 return sema.fail(
11529 block,11533 block,
...@@ -11556,8 +11560,8 @@ fn validateSwitchBlock(...@@ -11556,8 +11560,8 @@ fn validateSwitchBlock(
11556 },11560 },
11557 .bool, .void => |type_tag| {11561 .bool, .void => |type_tag| {
11558 const all_values_handled = switch (type_tag) {11562 const all_values_handled = switch (type_tag) {
11559 .bool => true_src != null and false_src != null,11563 .bool => seen.true_src != null and seen.false_src != null,
11560 .void => void_src != null,11564 .void => seen.void_src != null,
11561 else => unreachable,11565 else => unreachable,
11562 };11566 };
11563 if (has_else) {11567 if (has_else) {
...@@ -11584,13 +11588,7 @@ fn validateSwitchBlock(...@@ -11584,13 +11588,7 @@ fn validateSwitchBlock(
11584 }11588 }
1158511589
11586 return .{11590 return .{
11587 .seen_enum_fields = seen_enum_fields,11591 .seen = seen,
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
11594 .case_vals = case_vals.items,11592 .case_vals = case_vals.items,
11595 .else_case = else_case,11593 .else_case = else_case,
11596 .else_err_ty = else_err_ty,11594 .else_err_ty = else_err_ty,
...@@ -11768,7 +11766,7 @@ fn resolveSwitchBlock(...@@ -11768,7 +11766,7 @@ fn resolveSwitchBlock(
11768 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };11766 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
11769 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);11767 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
11770 if (tagged_union_originally) {11768 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| {
11772 if (maybe_seen != null) continue;11770 if (maybe_seen != null) continue;
11773 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;11771 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
11774 } else {11772 } else {
...@@ -12573,13 +12571,7 @@ fn validateSwitchItemOrRange(...@@ -12573,13 +12571,7 @@ fn validateSwitchItemOrRange(
12573 item_val: Value,12571 item_val: Value,
12574 opt_last_val: ?Value,12572 opt_last_val: ?Value,
12575 item_ty: Type,12573 item_ty: Type,
12576 seen_enum_fields: []?LazySrcLoc,12574 seen: *ValidatedSwitchBlock.Seen,
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,
12583) CompileError!void {12575) CompileError!void {
12584 const pt = sema.pt;12576 const pt = sema.pt;
12585 const zcu = pt.zcu;12577 const zcu = pt.zcu;
...@@ -12588,88 +12580,117 @@ fn validateSwitchItemOrRange(...@@ -12588,88 +12580,117 @@ fn validateSwitchItemOrRange(
12588 .@"enum" => {12580 .@"enum" => {
12589 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;12581 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
12590 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {12582 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
12591 const maybe_prev_src = seen_enum_fields[field_index];12583 const maybe_prev_src = seen.enum_fields[field_index];
12592 seen_enum_fields[field_index] = item_src;12584 seen.enum_fields[field_index] = item_src;
12593 break :maybe_prev_src maybe_prev_src;12585 break :maybe_prev_src maybe_prev_src;
12594 } else {12586 } 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(.{
12596 .first = .fromInterned(int),12589 .first = .fromInterned(int),
12597 .last = .fromInterned(int),12590 .last = .fromInterned(int),
12598 .src = item_src,12591 .src = item_src,
12599 }, .fromInterned(ip.typeOf(int)), zcu);12592 }, .fromInterned(ip.typeOf(int)), zcu)) |prev| prev.src else null;
12600 }12593 }
12601 },12594 },
12602 .error_set => {12595 .error_set => {
12603 const error_name = ip.indexToKey(item_val.toIntern()).err.name;12596 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|
12605 prev.value12598 prev.value
12606 else12599 else
12607 null;12600 null;
12608 },12601 },
12609 .int, .comptime_int => {12602 .int, .comptime_int => {
12610 if (opt_last_val) |last_val| {12603 const first_val = item_val;
12611 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;
12612 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {12606 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {
12613 return sema.fail(block, item_src, "range start value is greater than the end value", .{});12607 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
12614 }12608 }
12615 break :maybe_prev_src range_set.addAssumeCapacity(.{12609 break :last_val last_val;
12616 .first = first_val,12610 };
12617 .last = last_val,12611 if (seen.ranges.addAssumeCapacity(.{
12618 .src = item_src,12612 .first = first_val,
12619 }, item_ty, zcu);12613 .last = last_val,
12620 } else {12614 .src = item_src,
12621 break :maybe_prev_src range_set.addAssumeCapacity(.{12615 }, item_ty, zcu)) |prev_range| {
12622 .first = item_val,12616 const overlap_start = first_val.numberMax(prev_range.first, zcu);
12623 .last = item_val,12617 const overlap_end = last_val.numberMin(prev_range.last, zcu);
12624 .src = item_src,12618 if (overlap_start.eql(overlap_end, item_ty, zcu)) {
12625 }, 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 });
12626 }12648 }
12649 break :maybe_prev_src null;
12627 },12650 },
12628 .@"union", .@"struct" => {12651 .@"union", .@"struct" => {
12629 const backing_int_val = ip.indexToKey(item_val.toIntern()).bitpack.backing_int_val;12652 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(.{
12631 .first = .fromInterned(backing_int_val),12654 .first = .fromInterned(backing_int_val),
12632 .last = .fromInterned(backing_int_val),12655 .last = .fromInterned(backing_int_val),
12633 .src = item_src,12656 .src = item_src,
12634 }, item_ty.backingIntType(zcu), zcu);12657 }, item_ty.backingIntType(zcu), zcu)) |prev| prev.src else null;
12635 },12658 },
12636 .enum_literal, .@"fn", .pointer, .type => {12659 .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|
12638 prev.value12661 prev.value
12639 else12662 else
12640 null;12663 null;
12641 },12664 },
12642 .bool => {12665 .bool => {
12643 if (item_val.toBool()) {12666 if (item_val.toBool()) {
12644 if (true_src.*) |prev_src| break :maybe_prev_src prev_src;12667 if (seen.true_src) |prev_src| break :maybe_prev_src prev_src;
12645 true_src.* = item_src;12668 seen.true_src = item_src;
12646 } else {12669 } else {
12647 if (false_src.*) |prev_src| break :maybe_prev_src prev_src;12670 if (seen.false_src) |prev_src| break :maybe_prev_src prev_src;
12648 false_src.* = item_src;12671 seen.false_src = item_src;
12649 }12672 }
12650 break :maybe_prev_src null;12673 break :maybe_prev_src null;
12651 },12674 },
12652 .void => {12675 .void => {
12653 if (void_src.*) |prev_src| break :maybe_prev_src prev_src;12676 if (seen.void_src) |prev_src| break :maybe_prev_src prev_src;
12654 void_src.* = item_src;12677 seen.void_src = item_src;
12655 break :maybe_prev_src null;12678 break :maybe_prev_src null;
12656 },12679 },
12657 else => unreachable, // should have already checked for invalid types12680 else => unreachable, // should have already checked for invalid types
12658 };12681 };
12659 if (maybe_prev_src) |prev_src| {12682 if (maybe_prev_src) |prev_src| {
12660 return sema.failWithOwnedErrorMsg(block, msg: {12683 return sema.failWithOwnedErrorMsg(block, msg: {
12661 const msg = try sema.errMsg(12684 const msg = try sema.errMsg(item_src, "duplicate switch value '{f}'", .{
12662 item_src,12685 item_val.fmtValueSema(pt, sema),
12663 "duplicate switch value",12686 });
12664 .{},
12665 );
12666 errdefer msg.destroy(sema.gpa);12687 errdefer msg.destroy(sema.gpa);
12667 try sema.errNote(12688 try sema.errNote(prev_src, msg, "previous value here", .{});
12668 prev_src,12689 if (item_ty.zigTypeTag(zcu) == .type) {
12669 msg,12690 try sema.addDeclaredHereNote(msg, item_val.toType());
12670 "previous value here",12691 } else {
12671 .{},12692 try sema.addDeclaredHereNote(msg, item_ty);
12672 );12693 }
12673 break :msg msg;12694 break :msg msg;
12674 });12695 });
12675 }12696 }
test/cases/compile_errors/duplicate_boolean_switch_value.zig+2-2
...@@ -17,7 +17,7 @@ comptime {...@@ -17,7 +17,7 @@ comptime {
1717
18// error18// error
19//19//
20// :5:9: error: duplicate switch value20// :5:9: error: duplicate switch value 'true'
21// :3:9: note: previous value here21// :3:9: note: previous value here
22// :13:9: error: duplicate switch value22// :13:9: error: duplicate switch value 'false'
23// :11:9: note: previous value here23// :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 {...@@ -16,5 +16,5 @@ fn foo(x: i32) !void {
1616
17// error17// error
18//18//
19// :5:9: error: duplicate switch value19// :5:9: error: duplicate switch value 'error.Foo'
20// :3:9: note: previous value here20// :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 {...@@ -20,5 +20,6 @@ export fn entry() usize {
2020
21// error21// error
22//22//
23// :13:15: error: duplicate switch value23// :13:15: error: duplicate switch value '.Two'
24// :10:15: note: previous value here24// :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 {...@@ -21,5 +21,6 @@ export fn entry() usize {
2121
22// error22// error
23//23//
24// :13:15: error: duplicate switch value24// :13:15: error: duplicate switch value '.Two'
25// :10:15: note: previous value here25// :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 {...@@ -25,7 +25,7 @@ export fn entry() usize {
2525
26// error26// error
27//27//
28// :8:9: error: duplicate switch value28// :8:9: error: duplicate switch value 'error.Foo'
29// :5:9: note: previous value here29// :5:9: note: previous value here
30// :16:9: error: duplicate switch value30// :16:9: error: duplicate switch value 'error.Foo'
31// :13:9: note: previous value here31// :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 {...@@ -27,7 +27,7 @@ export fn entry() usize {
2727
28// error28// error
29//29//
30// :8:9: error: duplicate switch value30// :8:9: error: duplicate switch value 'error.Foo'
31// :5:9: note: previous value here31// :5:9: note: previous value here
32// :17:9: error: duplicate switch value32// :17:9: error: duplicate switch value 'error.Foo'
33// :14:9: note: previous value here33// :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 {...@@ -13,5 +13,5 @@ export fn entry() usize {
1313
14// error14// error
15//15//
16// :6:9: error: duplicate switch value16// :6:9: error: duplicate switch value 'u32'
17// :4:9: note: previous value here17// :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 {...@@ -17,5 +17,6 @@ export fn entry() usize {
1717
18// error18// error
19//19//
20// :10:9: error: duplicate switch value20// :10:9: error: duplicate switch value 'tmp.Test'
21// :8:9: note: previous value here21// :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 {...@@ -28,13 +28,43 @@ export fn entry4(x: u8) void {
28 }28 }
29}29}
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
31// error52// error
32//53//
33// :4:10: error: duplicate switch value54// :4:10: error: duplicate switch ranges
34// :3:10: note: previous value here55// :3:10: note: overlaps with previous range here
35// :11:10: error: duplicate switch value56// :3:10: note: ranges overlap from '1' to '2'
36// :10:13: note: previous value here57// :11:10: error: duplicate switch value '5'
37// :17:10: error: duplicate switch value58// :10:13: note: previous value inside range here
59// :17:10: error: duplicate switch value '5'
38// :18:9: note: previous value here60// :18:9: note: previous value here
39// :27:10: error: duplicate switch value61// :27:10: error: duplicate switch value '6'
40// :26:9: note: previous value here62// :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