Commit 94e98bfe80
Changed files (9)
src
src/Air/print.zig
@@ -363,7 +363,7 @@ const Writer = struct {
}
fn writeType(w: *Writer, s: *std.Io.Writer, ty: Type) !void {
- return ty.print(s, w.pt);
+ return ty.print(s, w.pt, null);
}
fn writeTy(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void {
src/codegen/spirv/CodeGen.zig
@@ -1213,7 +1213,7 @@ fn resolveTypeName(cg: *CodeGen, ty: Type) ![]const u8 {
const gpa = cg.module.gpa;
var aw: std.Io.Writer.Allocating = .init(gpa);
defer aw.deinit();
- ty.print(&aw.writer, cg.pt) catch |err| switch (err) {
+ ty.print(&aw.writer, cg.pt, null) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
};
return try aw.toOwnedSlice();
src/codegen/llvm.zig
@@ -2697,7 +2697,7 @@ pub const Object = struct {
fn allocTypeName(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error![:0]const u8 {
var aw: std.Io.Writer.Allocating = .init(o.gpa);
defer aw.deinit();
- ty.print(&aw.writer, pt) catch |err| switch (err) {
+ ty.print(&aw.writer, pt, null) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
};
return aw.toOwnedSliceSentinel(0);
src/print_value.zig
@@ -66,7 +66,7 @@ pub fn print(
.func_type,
.error_set_type,
.inferred_error_set_type,
- => try Type.print(val.toType(), writer, pt),
+ => try Type.print(val.toType(), writer, pt, null),
.undef => try writer.writeAll("undefined"),
.simple_value => |simple_value| switch (simple_value) {
.void => try writer.writeAll("{}"),
src/Sema.zig
@@ -2447,19 +2447,6 @@ fn failWithStructInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, t
});
}
-fn failWithErrorSetCodeMissing(
- sema: *Sema,
- block: *Block,
- src: LazySrcLoc,
- dest_err_set_ty: Type,
- src_err_set_ty: Type,
-) CompileError {
- const pt = sema.pt;
- return sema.fail(block, src, "expected type '{f}', found type '{f}'", .{
- dest_err_set_ty.fmt(pt), src_err_set_ty.fmt(pt),
- });
-}
-
pub fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: ?usize) CompileError {
const pt = sema.pt;
return sema.failWithOwnedErrorMsg(block, msg: {
@@ -2619,6 +2606,26 @@ pub fn errMsg(
return Zcu.ErrorMsg.create(sema.gpa, src, format, args);
}
+fn typeMismatchErrMsg(sema: *Sema, src: LazySrcLoc, expected: Type, found: Type) Allocator.Error!*Zcu.ErrorMsg {
+ const pt = sema.pt;
+ var cmp: Type.Comparison = try .init(&.{ expected, found }, pt);
+ defer cmp.deinit(pt);
+
+ const msg = try sema.errMsg(src, "expected type '{f}', found '{f}'", .{
+ cmp.fmtType(expected, pt),
+ cmp.fmtType(found, pt),
+ });
+ errdefer msg.destroy(sema.gpa);
+
+ for (cmp.type_dedupe_cache.keys(), cmp.type_dedupe_cache.values()) |ty, value| {
+ if (value == .dont_dedupe) continue;
+ const placeholder = value.dedupe;
+ try sema.errNote(src, msg, "{f} = {f}", .{ placeholder, ty.fmt(pt) });
+ }
+
+ return msg;
+}
+
pub fn fail(
sema: *Sema,
block: *Block,
@@ -2635,6 +2642,14 @@ pub fn fail(
return sema.failWithOwnedErrorMsg(block, err_msg);
}
+fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: Type, found: Type) CompileError {
+ const err_msg = try sema.typeMismatchErrMsg(src, expected, found);
+ errdefer err_msg.destroy(sema.gpa);
+ try addDeclaredHereNote(sema, err_msg, expected);
+ try addDeclaredHereNote(sema, err_msg, found);
+ return sema.failWithOwnedErrorMsg(block, err_msg);
+}
+
pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {
@branchHint(.cold);
const gpa = sema.gpa;
@@ -22933,7 +22948,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
const operand_is_vector = operand_ty.zigTypeTag(zcu) == .vector;
const dest_is_vector = dest_ty.zigTypeTag(zcu) == .vector;
if (operand_is_vector != dest_is_vector) {
- return sema.fail(block, operand_src, "expected type '{f}', found '{f}'", .{ dest_ty.fmt(pt), operand_ty.fmt(pt) });
+ return sema.failWithTypeMismatch(block, operand_src, dest_ty, operand_ty);
}
if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
@@ -29167,7 +29182,7 @@ fn coerceExtra(
}
const msg = msg: {
- const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{ dest_ty.fmt(pt), inst_ty.fmt(pt) });
+ const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
if (!can_coerce_to) {
@@ -30780,9 +30795,7 @@ fn coerceEnumToUnion(
const tag_ty = union_ty.unionTagType(zcu) orelse {
const msg = msg: {
- const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
- union_ty.fmt(pt), inst_ty.fmt(pt),
- });
+ const msg = try sema.typeMismatchErrMsg(inst_src, union_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(union_ty_src, msg, "cannot coerce enum to untagged union", .{});
try sema.addDeclaredHereNote(msg, union_ty);
@@ -30933,9 +30946,7 @@ fn coerceArrayLike(
const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen(zcu));
if (dest_len != inst_len) {
const msg = msg: {
- const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
- dest_ty.fmt(pt), inst_ty.fmt(pt),
- });
+ const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
@@ -31018,9 +31029,7 @@ fn coerceTupleToArray(
if (dest_len != inst_len) {
const msg = msg: {
- const msg = try sema.errMsg(inst_src, "expected type '{f}', found '{f}'", .{
- dest_ty.fmt(pt), inst_ty.fmt(pt),
- });
+ const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
@@ -32719,12 +32728,12 @@ fn wrapErrorUnionSet(
break :ok;
},
}
- return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
+ return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
else => switch (ip.indexToKey(dest_err_set_ty.toIntern())) {
.error_set_type => |error_set_type| ok: {
if (error_set_type.nameIndex(ip, expected_name) != null) break :ok;
- return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
+ return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
.inferred_error_set_type => |func_index| ok: {
// We carefully do this in an order that avoids unnecessarily
@@ -32740,7 +32749,7 @@ fn wrapErrorUnionSet(
},
}
- return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
+ return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
else => unreachable,
},
src/Type.zig
@@ -141,7 +141,7 @@ const Format = struct {
pt: Zcu.PerThread,
fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
- return print(f.ty, writer, f.pt);
+ return print(f.ty, writer, f.pt, null);
}
};
@@ -157,7 +157,17 @@ pub fn dump(start_type: Type, writer: *std.Io.Writer) std.Io.Writer.Error!void {
/// Prints a name suitable for `@typeName`.
/// TODO: take an `opt_sema` to pass to `fmtValue` when printing sentinels.
-pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.Error!void {
+pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread, ctx: ?*Comparison) std.Io.Writer.Error!void {
+ if (ctx) |c| {
+ const should_dedupe = shouldDedupeType(ty, c, pt) catch |err| switch (err) {
+ error.OutOfMemory => return error.WriteFailed,
+ };
+ switch (should_dedupe) {
+ .dont_dedupe => {},
+ .dedupe => |placeholder| return placeholder.format(writer),
+ }
+ }
+
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
switch (ip.indexToKey(ty.toIntern())) {
@@ -209,39 +219,39 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
if (info.flags.is_const) try writer.writeAll("const ");
if (info.flags.is_volatile) try writer.writeAll("volatile ");
- try print(Type.fromInterned(info.child), writer, pt);
+ try print(Type.fromInterned(info.child), writer, pt, ctx);
return;
},
.array_type => |array_type| {
if (array_type.sentinel == .none) {
try writer.print("[{d}]", .{array_type.len});
- try print(Type.fromInterned(array_type.child), writer, pt);
+ try print(Type.fromInterned(array_type.child), writer, pt, ctx);
} else {
try writer.print("[{d}:{f}]", .{
array_type.len,
Value.fromInterned(array_type.sentinel).fmtValue(pt),
});
- try print(Type.fromInterned(array_type.child), writer, pt);
+ try print(Type.fromInterned(array_type.child), writer, pt, ctx);
}
return;
},
.vector_type => |vector_type| {
try writer.print("@Vector({d}, ", .{vector_type.len});
- try print(Type.fromInterned(vector_type.child), writer, pt);
+ try print(Type.fromInterned(vector_type.child), writer, pt, ctx);
try writer.writeAll(")");
return;
},
.opt_type => |child| {
try writer.writeByte('?');
- return print(Type.fromInterned(child), writer, pt);
+ return print(Type.fromInterned(child), writer, pt, ctx);
},
.error_union_type => |error_union_type| {
- try print(Type.fromInterned(error_union_type.error_set_type), writer, pt);
+ try print(Type.fromInterned(error_union_type.error_set_type), writer, pt, ctx);
try writer.writeByte('!');
if (error_union_type.payload_type == .generic_poison_type) {
try writer.writeAll("anytype");
} else {
- try print(Type.fromInterned(error_union_type.payload_type), writer, pt);
+ try print(Type.fromInterned(error_union_type.payload_type), writer, pt, ctx);
}
return;
},
@@ -323,7 +333,7 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, val, i| {
try writer.writeAll(if (i == 0) " " else ", ");
if (val != .none) try writer.writeAll("comptime ");
- try print(Type.fromInterned(field_ty), writer, pt);
+ try print(Type.fromInterned(field_ty), writer, pt, ctx);
if (val != .none) try writer.print(" = {f}", .{Value.fromInterned(val).fmtValue(pt)});
}
try writer.writeAll(" }");
@@ -360,7 +370,7 @@ pub fn print(ty: Type, writer: *std.Io.Writer, pt: Zcu.PerThread) std.Io.Writer.
if (param_ty == .generic_poison_type) {
try writer.writeAll("anytype");
} else {
- try print(Type.fromInterned(param_ty), writer, pt);
+ try print(Type.fromInterned(param_ty), writer, pt, ctx);
}
}
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.
if (fn_info.return_type == .generic_poison_type) {
try writer.writeAll("anytype");
} else {
- try print(Type.fromInterned(fn_info.return_type), writer, pt);
+ try print(Type.fromInterned(fn_info.return_type), writer, pt, ctx);
}
},
.anyframe_type => |child| {
if (child == .none) return writer.writeAll("anyframe");
try writer.writeAll("anyframe->");
- return print(Type.fromInterned(child), writer, pt);
+ return print(Type.fromInterned(child), writer, pt, ctx);
},
// values, not types
@@ -4046,6 +4056,175 @@ pub fn isNullFromType(ty: Type, zcu: *const Zcu) ?bool {
return null;
}
+/// Recursively walks the type and marks for each subtype how many times it has been seen
+fn collectSubtypes(ty: Type, pt: Zcu.PerThread, visited: *std.AutoArrayHashMapUnmanaged(Type, u16)) error{OutOfMemory}!void {
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+
+ const gop = try visited.getOrPut(zcu.gpa, ty);
+ if (gop.found_existing) {
+ gop.value_ptr.* += 1;
+ } else {
+ gop.value_ptr.* = 1;
+ }
+
+ switch (ip.indexToKey(ty.toIntern())) {
+ .ptr_type => try collectSubtypes(Type.fromInterned(ty.ptrInfo(zcu).child), pt, visited),
+ .array_type => |array_type| try collectSubtypes(Type.fromInterned(array_type.child), pt, visited),
+ .vector_type => |vector_type| try collectSubtypes(Type.fromInterned(vector_type.child), pt, visited),
+ .opt_type => |child| try collectSubtypes(Type.fromInterned(child), pt, visited),
+ .error_union_type => |error_union_type| {
+ try collectSubtypes(Type.fromInterned(error_union_type.error_set_type), pt, visited);
+ if (error_union_type.payload_type != .generic_poison_type) {
+ try collectSubtypes(Type.fromInterned(error_union_type.payload_type), pt, visited);
+ }
+ },
+ .tuple_type => |tuple| {
+ for (tuple.types.get(ip)) |field_ty| {
+ try collectSubtypes(Type.fromInterned(field_ty), pt, visited);
+ }
+ },
+ .func_type => |fn_info| {
+ const param_types = fn_info.param_types.get(&zcu.intern_pool);
+ for (param_types) |param_ty| {
+ if (param_ty != .generic_poison_type) {
+ try collectSubtypes(Type.fromInterned(param_ty), pt, visited);
+ }
+ }
+
+ if (fn_info.return_type != .generic_poison_type) {
+ try collectSubtypes(Type.fromInterned(fn_info.return_type), pt, visited);
+ }
+ },
+ .anyframe_type => |child| try collectSubtypes(Type.fromInterned(child), pt, visited),
+
+ // leaf types
+ .undef,
+ .inferred_error_set_type,
+ .error_set_type,
+ .struct_type,
+ .union_type,
+ .opaque_type,
+ .enum_type,
+ .simple_type,
+ .int_type,
+ => {},
+
+ // values, not types
+ .simple_value,
+ .variable,
+ .@"extern",
+ .func,
+ .int,
+ .err,
+ .error_union,
+ .enum_literal,
+ .enum_tag,
+ .empty_enum_value,
+ .float,
+ .ptr,
+ .slice,
+ .opt,
+ .aggregate,
+ .un,
+ // memoization, not types
+ .memoized_call,
+ => unreachable,
+ }
+}
+
+fn shouldDedupeType(ty: Type, ctx: *Comparison, pt: Zcu.PerThread) error{OutOfMemory}!Comparison.DedupeEntry {
+ if (ctx.type_occurrences.get(ty)) |occ| {
+ if (ctx.type_dedupe_cache.get(ty)) |cached| {
+ return cached;
+ }
+
+ var discarding: std.Io.Writer.Discarding = .init(&.{});
+
+ print(ty, &discarding.writer, pt, null) catch
+ unreachable; // we are writing into a discarding writer, it should never fail
+
+ const type_len: i32 = @intCast(discarding.count);
+
+ const placeholder_len: i32 = 3;
+ const min_saved_bytes: i32 = 10;
+
+ const saved_bytes = (type_len - placeholder_len) * (occ - 1);
+ const max_placeholders = 7; // T to Z
+ const should_dedupe = saved_bytes >= min_saved_bytes and ctx.placeholder_index < max_placeholders;
+
+ const entry: Comparison.DedupeEntry = if (should_dedupe) b: {
+ ctx.placeholder_index += 1;
+ break :b .{ .dedupe = .{ .index = ctx.placeholder_index - 1 } };
+ } else .dont_dedupe;
+
+ try ctx.type_dedupe_cache.put(pt.zcu.gpa, ty, entry);
+
+ return entry;
+ } else {
+ return .{ .dont_dedupe = {} };
+ }
+}
+
+/// The comparison recursively walks all types given and notes how many times
+/// each subtype occurs. It then while recursively printing decides for each
+/// subtype whether to print the type inline or create a placeholder based on
+/// the subtype length and number of occurences. Placeholders are then found by
+/// iterating `type_dedupe_cache` which caches the inline/placeholder decisions.
+pub const Comparison = struct {
+ type_occurrences: std.AutoArrayHashMapUnmanaged(Type, u16),
+ type_dedupe_cache: std.AutoArrayHashMapUnmanaged(Type, DedupeEntry),
+ placeholder_index: u8,
+
+ pub const Placeholder = struct {
+ index: u8,
+
+ pub fn format(p: Placeholder, writer: *std.Io.Writer) error{WriteFailed}!void {
+ return writer.print("<{c}>", .{p.index + 'T'});
+ }
+ };
+
+ pub const DedupeEntry = union(enum) {
+ dont_dedupe: void,
+ dedupe: Placeholder,
+ };
+
+ pub fn init(types: []const Type, pt: Zcu.PerThread) error{OutOfMemory}!Comparison {
+ var cmp: Comparison = .{
+ .type_occurrences = .empty,
+ .type_dedupe_cache = .empty,
+ .placeholder_index = 0,
+ };
+
+ errdefer cmp.deinit(pt);
+
+ for (types) |ty| {
+ try collectSubtypes(ty, pt, &cmp.type_occurrences);
+ }
+
+ return cmp;
+ }
+
+ pub fn deinit(cmp: *Comparison, pt: Zcu.PerThread) void {
+ const gpa = pt.zcu.gpa;
+ cmp.type_occurrences.deinit(gpa);
+ cmp.type_dedupe_cache.deinit(gpa);
+ }
+
+ pub fn fmtType(ctx: *Comparison, ty: Type, pt: Zcu.PerThread) Comparison.Formatter {
+ return .{ .ty = ty, .ctx = ctx, .pt = pt };
+ }
+ pub const Formatter = struct {
+ ty: Type,
+ ctx: *Comparison,
+ pt: Zcu.PerThread,
+
+ pub fn format(self: Comparison.Formatter, writer: anytype) error{WriteFailed}!void {
+ print(self.ty, writer, self.pt, self.ctx) catch return error.WriteFailed;
+ }
+ };
+};
+
pub const @"u1": Type = .{ .ip_index = .u1_type };
pub const @"u8": Type = .{ .ip_index = .u8_type };
pub const @"u16": Type = .{ .ip_index = .u16_type };
test/cases/compile_errors/pointer_attributes_checked_when_coercing_pointer_to_anon_literal.zig
@@ -16,7 +16,8 @@ comptime {
//
// :2:29: error: expected type '[][]const u8', found '*const [2][]const u8'
// :2:29: note: cast discards const qualifier
-// :6:31: error: expected type '*[2][]const u8', found '*const [2][]const u8'
+// :6:31: error: expected type '*<T>', found '*const <T>'
+// :6:31: note: <T> = [2][]const u8
// :6:31: note: cast discards const qualifier
// :11:19: error: expected type '*tmp.S', found '*const tmp.S'
// :11:19: note: cast discards const qualifier
test/cases/compile_errors/type_dedupe.zig
@@ -0,0 +1,18 @@
+const SomeVeryLongName = struct {};
+
+fn foo(a: *SomeVeryLongName) void {
+ _ = a;
+}
+
+export fn entry() void {
+ const a: SomeVeryLongName = .{};
+
+ foo(a);
+}
+
+// error
+//
+// :10:9: error: expected type '*<T>', found '<T>'
+// :10:9: note: <T> = tmp.SomeVeryLongName
+// :1:26: note: struct declared here
+// :3:11: note: parameter type declared here
test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig
@@ -5,4 +5,5 @@ export fn entry() void {
// error
//
-// :3:11: error: expected type '@TypeOf(.{})', found 'struct { comptime comptime_int = 1, comptime comptime_int = 2, comptime comptime_int = 3 }'
+// :3:11: error: expected type '@TypeOf(.{})', found 'struct { comptime <T> = 1, comptime <T> = 2, comptime <T> = 3 }'
+// :3:11: note: <T> = comptime_int