authorgravatar for prokop@rdck.devProkop Randáček <prokop@rdck.dev> 2025-11-16 14:20:45+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-11-16 16:20:45+02:00
log94e98bfe80033439382bff22bcab02aaaa580dc5
treecb8b5e94a0a32e9b7eeb2c401ef0732db0d7dace
parentaa4332fb0e5f46657c8c41a3971150b642be1c19
signaturebadge-check Signed by PGP key B5690EEEBB952194

Dedupe types when printing error messages


9 files changed, 254 insertions(+), 46 deletions(-)

src/Air/print.zig+1-1
......@@ -363,7 +363,7 @@ const Writer = struct {
363363 }
364364
365365 fn writeType(w: *Writer, s: *std.Io.Writer, ty: Type) !void {
366 return ty.print(s, w.pt);
366 return ty.print(s, w.pt, null);
367367 }
368368
369369 fn writeTy(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
src/Sema.zig+36-27
......@@ -2447,19 +2447,6 @@ fn failWithStructInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, t
24472447 });
24482448}
24492449
2450fn failWithErrorSetCodeMissing(
2451 sema: *Sema,
2452 block: *Block,
2453 src: LazySrcLoc,
2454 dest_err_set_ty: Type,
2455 src_err_set_ty: Type,
2456) CompileError {
2457 const pt = sema.pt;
2458 return sema.fail(block, src, "expected type '{f}', found type '{f}'", .{
2459 dest_err_set_ty.fmt(pt), src_err_set_ty.fmt(pt),
2460 });
2461}
2462
24632450pub fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: ?usize) CompileError {
24642451 const pt = sema.pt;
24652452 return sema.failWithOwnedErrorMsg(block, msg: {
......@@ -2619,6 +2606,26 @@ pub fn errMsg(
26192606 return Zcu.ErrorMsg.create(sema.gpa, src, format, args);
26202607}
26212608
2609fn typeMismatchErrMsg(sema: *Sema, src: LazySrcLoc, expected: Type, found: Type) Allocator.Error!*Zcu.ErrorMsg {
2610 const pt = sema.pt;
2611 var cmp: Type.Comparison = try .init(&.{ expected, found }, pt);
2612 defer cmp.deinit(pt);
2613
2614 const msg = try sema.errMsg(src, "expected type '{f}', found '{f}'", .{
2615 cmp.fmtType(expected, pt),
2616 cmp.fmtType(found, pt),
2617 });
2618 errdefer msg.destroy(sema.gpa);
2619
2620 for (cmp.type_dedupe_cache.keys(), cmp.type_dedupe_cache.values()) |ty, value| {
2621 if (value == .dont_dedupe) continue;
2622 const placeholder = value.dedupe;
2623 try sema.errNote(src, msg, "{f} = {f}", .{ placeholder, ty.fmt(pt) });
2624 }
2625
2626 return msg;
2627}
2628
26222629pub fn fail(
26232630 sema: *Sema,
26242631 block: *Block,
......@@ -2635,6 +2642,14 @@ pub fn fail(
26352642 return sema.failWithOwnedErrorMsg(block, err_msg);
26362643}
26372644
2645fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: Type, found: Type) CompileError {
2646 const err_msg = try sema.typeMismatchErrMsg(src, expected, found);
2647 errdefer err_msg.destroy(sema.gpa);
2648 try addDeclaredHereNote(sema, err_msg, expected);
2649 try addDeclaredHereNote(sema, err_msg, found);
2650 return sema.failWithOwnedErrorMsg(block, err_msg);
2651}
2652
26382653pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {
26392654 @branchHint(.cold);
26402655 const gpa = sema.gpa;
......@@ -22933,7 +22948,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
2293322948 const operand_is_vector = operand_ty.zigTypeTag(zcu) == .vector;
2293422949 const dest_is_vector = dest_ty.zigTypeTag(zcu) == .vector;
2293522950 if (operand_is_vector != dest_is_vector) {
22936 return sema.fail(block, operand_src, "expected type '{f}', found '{f}'", .{ dest_ty.fmt(pt), operand_ty.fmt(pt) });
22951 return sema.failWithTypeMismatch(block, operand_src, dest_ty, operand_ty);
2293722952 }
2293822953
2293922954 if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
......@@ -29167,7 +29182,7 @@ fn coerceExtra(
2916729182 }
2916829183
2916929184 const msg = msg: {
29170 const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{ dest_ty.fmt(pt), inst_ty.fmt(pt) });
29185 const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
2917129186 errdefer msg.destroy(sema.gpa);
2917229187
2917329188 if (!can_coerce_to) {
......@@ -30780,9 +30795,7 @@ fn coerceEnumToUnion(
3078030795
3078130796 const tag_ty = union_ty.unionTagType(zcu) orelse {
3078230797 const msg = msg: {
30783 const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
30784 union_ty.fmt(pt), inst_ty.fmt(pt),
30785 });
30798 const msg = try sema.typeMismatchErrMsg(inst_src, union_ty, inst_ty);
3078630799 errdefer msg.destroy(sema.gpa);
3078730800 try sema.errNote(union_ty_src, msg, "cannot coerce enum to untagged union", .{});
3078830801 try sema.addDeclaredHereNote(msg, union_ty);
......@@ -30933,9 +30946,7 @@ fn coerceArrayLike(
3093330946 const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen(zcu));
3093430947 if (dest_len != inst_len) {
3093530948 const msg = msg: {
30936 const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
30937 dest_ty.fmt(pt), inst_ty.fmt(pt),
30938 });
30949 const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
3093930950 errdefer msg.destroy(sema.gpa);
3094030951 try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
3094130952 try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
......@@ -31018,9 +31029,7 @@ fn coerceTupleToArray(
3101831029
3101931030 if (dest_len != inst_len) {
3102031031 const msg = msg: {
31021 const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
31022 dest_ty.fmt(pt), inst_ty.fmt(pt),
31023 });
31032 const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
3102431033 errdefer msg.destroy(sema.gpa);
3102531034 try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
3102631035 try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
......@@ -32719,12 +32728,12 @@ fn wrapErrorUnionSet(
3271932728 break :ok;
3272032729 },
3272132730 }
32722 return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
32731 return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
3272332732 },
3272432733 else => switch (ip.indexToKey(dest_err_set_ty.toIntern())) {
3272532734 .error_set_type => |error_set_type| ok: {
3272632735 if (error_set_type.nameIndex(ip, expected_name) != null) break :ok;
32727 return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
32736 return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
3272832737 },
3272932738 .inferred_error_set_type => |func_index| ok: {
3273032739 // We carefully do this in an order that avoids unnecessarily
......@@ -32740,7 +32749,7 @@ fn wrapErrorUnionSet(
3274032749 },
3274132750 }
3274232751
32743 return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
32752 return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
3274432753 },
3274532754 else => unreachable,
3274632755 },
src/Type.zig+192-13
......@@ -141,7 +141,7 @@ const Format = struct {
141141 pt: Zcu.PerThread,
142142
143143 fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
144 return print(f.ty, writer, f.pt);
144 return print(f.ty, writer, f.pt, null);
145145 }
146146};
147147
......@@ -157,7 +157,17 @@ pub fn dump(start_type: Type, writer: *std.Io.Writer) std.Io.Writer.Error!void {
157157
158158/// Prints a name suitable for `@typeName`.
159159/// TODO: take an `opt_sema` to pass to `fmtValue` when printing sentinels.
160pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.Error!void {
160pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread, ctx: ?*Comparison) std.Io.Writer.Error!void {
161 if (ctx) |c| {
162 const should_dedupe = shouldDedupeType(ty, c, pt) catch |err| switch (err) {
163 error.OutOfMemory => return error.WriteFailed,
164 };
165 switch (should_dedupe) {
166 .dont_dedupe => {},
167 .dedupe => |placeholder| return placeholder.format(writer),
168 }
169 }
170
161171 const zcu = pt.zcu;
162172 const ip = &zcu.intern_pool;
163173 switch (ip.indexToKey(ty.toIntern())) {
......@@ -209,39 +219,39 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
209219 if (info.flags.is_const) try writer.writeAll("const ");
210220 if (info.flags.is_volatile) try writer.writeAll("volatile ");
211221
212 try print(Type.fromInterned(info.child), writer, pt);
222 try print(Type.fromInterned(info.child), writer, pt, ctx);
213223 return;
214224 },
215225 .array_type => |array_type| {
216226 if (array_type.sentinel == .none) {
217227 try writer.print("[{d}]", .{array_type.len});
218 try print(Type.fromInterned(array_type.child), writer, pt);
228 try print(Type.fromInterned(array_type.child), writer, pt, ctx);
219229 } else {
220230 try writer.print("[{d}:{f}]", .{
221231 array_type.len,
222232 Value.fromInterned(array_type.sentinel).fmtValue(pt),
223233 });
224 try print(Type.fromInterned(array_type.child), writer, pt);
234 try print(Type.fromInterned(array_type.child), writer, pt, ctx);
225235 }
226236 return;
227237 },
228238 .vector_type => |vector_type| {
229239 try writer.print("@Vector({d}, ", .{vector_type.len});
230 try print(Type.fromInterned(vector_type.child), writer, pt);
240 try print(Type.fromInterned(vector_type.child), writer, pt, ctx);
231241 try writer.writeAll(")");
232242 return;
233243 },
234244 .opt_type => |child| {
235245 try writer.writeByte('?');
236 return print(Type.fromInterned(child), writer, pt);
246 return print(Type.fromInterned(child), writer, pt, ctx);
237247 },
238248 .error_union_type => |error_union_type| {
239 try print(Type.fromInterned(error_union_type.error_set_type), writer, pt);
249 try print(Type.fromInterned(error_union_type.error_set_type), writer, pt, ctx);
240250 try writer.writeByte('!');
241251 if (error_union_type.payload_type == .generic_poison_type) {
242252 try writer.writeAll("anytype");
243253 } else {
244 try print(Type.fromInterned(error_union_type.payload_type), writer, pt);
254 try print(Type.fromInterned(error_union_type.payload_type), writer, pt, ctx);
245255 }
246256 return;
247257 },
......@@ -323,7 +333,7 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
323333 for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, val, i| {
324334 try writer.writeAll(if (i == 0) " " else ", ");
325335 if (val != .none) try writer.writeAll("comptime ");
326 try print(Type.fromInterned(field_ty), writer, pt);
336 try print(Type.fromInterned(field_ty), writer, pt, ctx);
327337 if (val != .none) try writer.print(" = {f}", .{Value.fromInterned(val).fmtValue(pt)});
328338 }
329339 try writer.writeAll(" }");
......@@ -360,7 +370,7 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
360370 if (param_ty == .generic_poison_type) {
361371 try writer.writeAll("anytype");
362372 } else {
363 try print(Type.fromInterned(param_ty), writer, pt);
373 try print(Type.fromInterned(param_ty), writer, pt, ctx);
364374 }
365375 }
366376 if (fn_info.is_var_args) {
......@@ -387,13 +397,13 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
387397 if (fn_info.return_type == .generic_poison_type) {
388398 try writer.writeAll("anytype");
389399 } else {
390 try print(Type.fromInterned(fn_info.return_type), writer, pt);
400 try print(Type.fromInterned(fn_info.return_type), writer, pt, ctx);
391401 }
392402 },
393403 .anyframe_type => |child| {
394404 if (child == .none) return writer.writeAll("anyframe");
395405 try writer.writeAll("anyframe->");
396 return print(Type.fromInterned(child), writer, pt);
406 return print(Type.fromInterned(child), writer, pt, ctx);
397407 },
398408
399409 // values, not types
......@@ -4046,6 +4056,175 @@ pub fn isNullFromType(ty: Type, zcu: *const Zcu) ?bool {
40464056 return null;
40474057}
40484058
4059/// Recursively walks the type and marks for each subtype how many times it has been seen
4060fn collectSubtypes(ty: Type, pt: Zcu.PerThread, visited: *std.AutoArrayHashMapUnmanaged(Type, u16)) error{OutOfMemory}!void {
4061 const zcu = pt.zcu;
4062 const ip = &zcu.intern_pool;
4063
4064 const gop = try visited.getOrPut(zcu.gpa, ty);
4065 if (gop.found_existing) {
4066 gop.value_ptr.* += 1;
4067 } else {
4068 gop.value_ptr.* = 1;
4069 }
4070
4071 switch (ip.indexToKey(ty.toIntern())) {
4072 .ptr_type => try collectSubtypes(Type.fromInterned(ty.ptrInfo(zcu).child), pt, visited),
4073 .array_type => |array_type| try collectSubtypes(Type.fromInterned(array_type.child), pt, visited),
4074 .vector_type => |vector_type| try collectSubtypes(Type.fromInterned(vector_type.child), pt, visited),
4075 .opt_type => |child| try collectSubtypes(Type.fromInterned(child), pt, visited),
4076 .error_union_type => |error_union_type| {
4077 try collectSubtypes(Type.fromInterned(error_union_type.error_set_type), pt, visited);
4078 if (error_union_type.payload_type != .generic_poison_type) {
4079 try collectSubtypes(Type.fromInterned(error_union_type.payload_type), pt, visited);
4080 }
4081 },
4082 .tuple_type => |tuple| {
4083 for (tuple.types.get(ip)) |field_ty| {
4084 try collectSubtypes(Type.fromInterned(field_ty), pt, visited);
4085 }
4086 },
4087 .func_type => |fn_info| {
4088 const param_types = fn_info.param_types.get(&zcu.intern_pool);
4089 for (param_types) |param_ty| {
4090 if (param_ty != .generic_poison_type) {
4091 try collectSubtypes(Type.fromInterned(param_ty), pt, visited);
4092 }
4093 }
4094
4095 if (fn_info.return_type != .generic_poison_type) {
4096 try collectSubtypes(Type.fromInterned(fn_info.return_type), pt, visited);
4097 }
4098 },
4099 .anyframe_type => |child| try collectSubtypes(Type.fromInterned(child), pt, visited),
4100
4101 // leaf types
4102 .undef,
4103 .inferred_error_set_type,
4104 .error_set_type,
4105 .struct_type,
4106 .union_type,
4107 .opaque_type,
4108 .enum_type,
4109 .simple_type,
4110 .int_type,
4111 => {},
4112
4113 // values, not types
4114 .simple_value,
4115 .variable,
4116 .@"extern",
4117 .func,
4118 .int,
4119 .err,
4120 .error_union,
4121 .enum_literal,
4122 .enum_tag,
4123 .empty_enum_value,
4124 .float,
4125 .ptr,
4126 .slice,
4127 .opt,
4128 .aggregate,
4129 .un,
4130 // memoization, not types
4131 .memoized_call,
4132 => unreachable,
4133 }
4134}
4135
4136fn shouldDedupeType(ty: Type, ctx: *Comparison, pt: Zcu.PerThread) error{OutOfMemory}!Comparison.DedupeEntry {
4137 if (ctx.type_occurrences.get(ty)) |occ| {
4138 if (ctx.type_dedupe_cache.get(ty)) |cached| {
4139 return cached;
4140 }
4141
4142 var discarding: std.Io.Writer.Discarding = .init(&.{});
4143
4144 print(ty, &discarding.writer, pt, null) catch
4145 unreachable; // we are writing into a discarding writer, it should never fail
4146
4147 const type_len: i32 = @intCast(discarding.count);
4148
4149 const placeholder_len: i32 = 3;
4150 const min_saved_bytes: i32 = 10;
4151
4152 const saved_bytes = (type_len - placeholder_len) * (occ - 1);
4153 const max_placeholders = 7; // T to Z
4154 const should_dedupe = saved_bytes >= min_saved_bytes and ctx.placeholder_index < max_placeholders;
4155
4156 const entry: Comparison.DedupeEntry = if (should_dedupe) b: {
4157 ctx.placeholder_index += 1;
4158 break :b .{ .dedupe = .{ .index = ctx.placeholder_index - 1 } };
4159 } else .dont_dedupe;
4160
4161 try ctx.type_dedupe_cache.put(pt.zcu.gpa, ty, entry);
4162
4163 return entry;
4164 } else {
4165 return .{ .dont_dedupe = {} };
4166 }
4167}
4168
4169/// The comparison recursively walks all types given and notes how many times
4170/// each subtype occurs. It then while recursively printing decides for each
4171/// subtype whether to print the type inline or create a placeholder based on
4172/// the subtype length and number of occurences. Placeholders are then found by
4173/// iterating `type_dedupe_cache` which caches the inline/placeholder decisions.
4174pub const Comparison = struct {
4175 type_occurrences: std.AutoArrayHashMapUnmanaged(Type, u16),
4176 type_dedupe_cache: std.AutoArrayHashMapUnmanaged(Type, DedupeEntry),
4177 placeholder_index: u8,
4178
4179 pub const Placeholder = struct {
4180 index: u8,
4181
4182 pub fn format(p: Placeholder, writer: *std.Io.Writer) error{WriteFailed}!void {
4183 return writer.print("<{c}>", .{p.index + 'T'});
4184 }
4185 };
4186
4187 pub const DedupeEntry = union(enum) {
4188 dont_dedupe: void,
4189 dedupe: Placeholder,
4190 };
4191
4192 pub fn init(types: []const Type, pt: Zcu.PerThread) error{OutOfMemory}!Comparison {
4193 var cmp: Comparison = .{
4194 .type_occurrences = .empty,
4195 .type_dedupe_cache = .empty,
4196 .placeholder_index = 0,
4197 };
4198
4199 errdefer cmp.deinit(pt);
4200
4201 for (types) |ty| {
4202 try collectSubtypes(ty, pt, &cmp.type_occurrences);
4203 }
4204
4205 return cmp;
4206 }
4207
4208 pub fn deinit(cmp: *Comparison, pt: Zcu.PerThread) void {
4209 const gpa = pt.zcu.gpa;
4210 cmp.type_occurrences.deinit(gpa);
4211 cmp.type_dedupe_cache.deinit(gpa);
4212 }
4213
4214 pub fn fmtType(ctx: *Comparison, ty: Type, pt: Zcu.PerThread) Comparison.Formatter {
4215 return .{ .ty = ty, .ctx = ctx, .pt = pt };
4216 }
4217 pub const Formatter = struct {
4218 ty: Type,
4219 ctx: *Comparison,
4220 pt: Zcu.PerThread,
4221
4222 pub fn format(self: Comparison.Formatter, writer: anytype) error{WriteFailed}!void {
4223 print(self.ty, writer, self.pt, self.ctx) catch return error.WriteFailed;
4224 }
4225 };
4226};
4227
40494228pub const @"u1": Type = .{ .ip_index = .u1_type };
40504229pub const @"u8": Type = .{ .ip_index = .u8_type };
40514230pub const @"u16": Type = .{ .ip_index = .u16_type };
src/codegen/llvm.zig+1-1
......@@ -2697,7 +2697,7 @@ pub const Object = struct {
26972697 fn allocTypeName(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error![:0]const u8 {
26982698 var aw: std.Io.Writer.Allocating = .init(o.gpa);
26992699 defer aw.deinit();
2700 ty.print(&aw.writer, pt) catch |err| switch (err) {
2700 ty.print(&aw.writer, pt, null) catch |err| switch (err) {
27012701 error.WriteFailed => return error.OutOfMemory,
27022702 };
27032703 return aw.toOwnedSliceSentinel(0);
src/codegen/spirv/CodeGen.zig+1-1
......@@ -1213,7 +1213,7 @@ fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 {
12131213 const gpa = cg.module.gpa;
12141214 var aw: std.Io.Writer.Allocating = .init(gpa);
12151215 defer aw.deinit();
1216 ty.print(&aw.writer, cg.pt) catch |err| switch (err) {
1216 ty.print(&aw.writer, cg.pt, null) catch |err| switch (err) {
12171217 error.WriteFailed => return error.OutOfMemory,
12181218 };
12191219 return try aw.toOwnedSlice();
src/print_value.zig+1-1
......@@ -66,7 +66,7 @@ pub fn print(
6666 .func_type,
6767 .error_set_type,
6868 .inferred_error_set_type,
69 => try Type.print(val.toType(), writer, pt),
69 => try Type.print(val.toType(), writer, pt, null),
7070 .undef => try writer.writeAll("undefined"),
7171 .simple_value => |simple_value| switch (simple_value) {
7272 .void => try writer.writeAll("{}"),
test/cases/compile_errors/pointer_attributes_checked_when_coercing_pointer_to_anon_literal.zig+2-1
......@@ -16,7 +16,8 @@ comptime {
1616//
1717// :2:29: error: expected type '[][]const u8', found '*const [2][]const u8'
1818// :2:29: note: cast discards const qualifier
19// :6:31: error: expected type '*[2][]const u8', found '*const [2][]const u8'
19// :6:31: error: expected type '*<T>', found '*const <T>'
20// :6:31: note: <T> = [2][]const u8
2021// :6:31: note: cast discards const qualifier
2122// :11:19: error: expected type '*tmp.S', found '*const tmp.S'
2223// :11:19: note: cast discards const qualifier
test/cases/compile_errors/type_dedupe.zig created+18
......@@ -0,0 +1,18 @@
1const SomeVeryLongName = struct {};
2
3fn foo(a: *SomeVeryLongName) void {
4 _ = a;
5}
6
7export fn entry() void {
8 const a: SomeVeryLongName = .{};
9
10 foo(a);
11}
12
13// error
14//
15// :10:9: error: expected type '*<T>', found '<T>'
16// :10:9: note: <T> = tmp.SomeVeryLongName
17// :1:26: note: struct declared here
18// :3:11: note: parameter type declared here
test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig+2-1
......@@ -5,4 +5,5 @@ export fn entry() void {
55
66// error
77//
8// :3:11: error: expected type '@TypeOf(.{})', found 'struct { comptime comptime_int = 1, comptime comptime_int = 2, comptime comptime_int = 3 }'
8// :3:11: error: expected type '@TypeOf(.{})', found 'struct { comptime <T> = 1, comptime <T> = 2, comptime <T> = 3 }'
9// :3:11: note: <T> = comptime_int