Skip to content
107 changes: 87 additions & 20 deletions src/analyser/InternPool.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub const Key = union(enum) {
pub const ErrorSet = struct {
owner_decl: Decl.OptionalIndex,
names: StringSlice,
flags: Flags,

pub const Flags = bitpack struct(u32) { has_unknown: bool, _: u31 = 0 };
};

pub const Function = struct {
Expand Down Expand Up @@ -434,6 +437,7 @@ pub const Key = union(enum) {
const b_info = b.error_set_type;

if (a_info.owner_decl != b_info.owner_decl) return false;
if (a_info.flags.has_unknown != b_info.flags.has_unknown) return false;

if (a_info.names.len != b_info.names.len) return false;

Expand Down Expand Up @@ -1564,6 +1568,7 @@ fn addExtra(ip: *InternPool, comptime T: type, extra: T) Allocator.Error!u32 {
Key.Pointer.Flags,
Key.Pointer.PackedOffset,
Key.Function.Flags,
Key.ErrorSet.Flags,
=> ip.extra.appendAssumeCapacity(@bitCast(item)),

u64,
Expand Down Expand Up @@ -1605,6 +1610,7 @@ fn extraData(ip: *const InternPool, comptime T: type, index: u32) T {
Key.Pointer.Flags,
Key.Pointer.PackedOffset,
Key.Function.Flags,
Key.ErrorSet.Flags,
=> @bitCast(item),

u64,
Expand Down Expand Up @@ -3221,7 +3227,7 @@ fn isUnknownDeepInternal(
},
.optional_type => |optional_info| try ip.isUnknownDeepInternal(optional_info.payload_type, set),
.error_union_type => |error_union_info| try ip.isUnknownDeepInternal(error_union_info.payload_type, set),
.error_set_type => false,
.error_set_type => return false,
.enum_type => |enum_index| {
const enum_info = ip.getEnum(enum_index);
for (enum_info.values.keys()) |val| {
Expand Down Expand Up @@ -3544,38 +3550,82 @@ pub fn scalarType(ip: *InternPool, ty: Index) Index {
}

pub fn errorSetMerge(ip: *InternPool, a_ty: Index, b_ty: Index) Allocator.Error!Index {
assert(ip.zigTypeTag(a_ty) == .error_set);
assert(ip.zigTypeTag(b_ty) == .error_set);
const has_unknown = ip.isUnknown(a_ty) or ip.isUnknown(b_ty);

// Anything merged with anyerror is anyerror.
if (a_ty == .anyerror_type or b_ty == .anyerror_type) {
return .anyerror_type;
}

if (a_ty == b_ty) return a_ty;
if (has_unknown) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is unnecessary and can be safely removed.

if (ip.isUnknown(a_ty) and ip.isUnknown(b_ty)) {
return try ip.get(.{
.error_set_type = .{
.owner_decl = .none,
.names = .empty,
.flags = .{
.has_unknown = true,
},
},
});
}

if (ip.isUnknown(a_ty)) {
assert(ip.zigTypeTag(b_ty) == .error_set);
const names = ip.indexToKey(b_ty).error_set_type.names;
return try ip.get(.{
.error_set_type = .{
.owner_decl = .none,
.names = names,
.flags = .{
.has_unknown = true,
},
},
});
}

if (ip.isUnknown(b_ty)) {
assert(ip.zigTypeTag(a_ty) == .error_set);
const names = ip.indexToKey(a_ty).error_set_type.names;
return try ip.get(.{
.error_set_type = .{
.owner_decl = .none,
.names = names,
.flags = .{
.has_unknown = true,
},
},
});
}

unreachable;
} else {
assert(ip.zigTypeTag(a_ty) == .error_set);
assert(ip.zigTypeTag(b_ty) == .error_set);

const a_names = try ip.indexToKey(a_ty).error_set_type.names.dupe(ip.gpa, ip);
defer ip.gpa.free(a_names);
if (a_ty == b_ty) return a_ty;

const b_names = try ip.indexToKey(b_ty).error_set_type.names.dupe(ip.gpa, ip);
defer ip.gpa.free(b_names);
const a_names = try ip.indexToKey(a_ty).error_set_type.names.dupe(ip.gpa, ip);
defer ip.gpa.free(a_names);

var set: std.array_hash_map.Auto(String, void) = .empty;
defer set.deinit(ip.gpa);
const b_names = try ip.indexToKey(b_ty).error_set_type.names.dupe(ip.gpa, ip);
defer ip.gpa.free(b_names);

try set.ensureTotalCapacity(ip.gpa, a_names.len + b_names.len);
var set: std.array_hash_map.Auto(String, void) = .empty;
defer set.deinit(ip.gpa);

for (a_names) |name| set.putAssumeCapacityNoClobber(name, {});
for (b_names) |name| set.putAssumeCapacity(name, {});
try set.ensureTotalCapacity(ip.gpa, a_names.len + b_names.len);

ip.string_pool.sortStrings(ip.io, set.keys());
for (a_names) |name| set.putAssumeCapacityNoClobber(name, {});
for (b_names) |name| set.putAssumeCapacity(name, {});

return try ip.get(.{
.error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(set.keys()),
},
});
ip.string_pool.sortStrings(ip.io, set.keys());

const a_or_b_has_unknown = ip.indexToKey(a_ty).error_set_type.flags.has_unknown or ip.indexToKey(b_ty).error_set_type.flags.has_unknown;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is best to avoid unnecessarily calling indexToKey. There is a previous call to ip.indexToKey(a_ty) and ip.indexToKey(b_ty) that should be reused.

return try ip.get(.{
.error_set_type = .{ .owner_decl = .none, .names = try ip.getStringSlice(set.keys()), .flags = .{ .has_unknown = a_or_b_has_unknown } },
});
}
}

/// Asserts the type is a vector or tuple.
Expand Down Expand Up @@ -4041,6 +4091,12 @@ fn printInternal(ip: *InternPool, ty: Index, writer: *std.Io.Writer, options: Fo
const name = error_set_info.names.at(@intCast(i), ip);
try writer.print("{f}", .{ip.fmtId(name)});
}
if (error_set_info.flags.has_unknown) {
if (error_set_info.names.len > 0) {
try writer.writeByte(',');
}
try writer.writeAll(" ... ");
}
try writer.writeByte('}');
},
.enum_type => return panicOrElse(?Index, "TODO", null),
Expand Down Expand Up @@ -4583,16 +4639,19 @@ test "error set type" {
const empty_error_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = StringSlice.empty,
.flags = .{ .has_unknown = false },
} });

const foo_bar_baz_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{ foo_name, bar_name, baz_name }),
.flags = .{ .has_unknown = false },
} });

const foo_bar_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{ foo_name, bar_name }),
.flags = .{ .has_unknown = false },
} });

try expect(empty_error_set != foo_bar_baz_set);
Expand All @@ -4613,6 +4672,7 @@ test "error union type" {
const empty_error_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = StringSlice.empty,
.flags = .{ .has_unknown = false },
} });
const bool_type = try ip.get(.{ .simple_type = .bool });

Expand Down Expand Up @@ -4979,18 +5039,22 @@ test "coerceInMemoryAllowed error set" {
const foo_bar_baz_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{ baz_name, bar_name, foo_name }),
.flags = .{ .has_unknown = false },
} });
const foo_bar_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{ foo_name, bar_name }),
.flags = .{ .has_unknown = false },
} });
const foo_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{foo_name}),
.flags = .{ .has_unknown = false },
} });
const empty_set = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = StringSlice.empty,
.flags = .{ .has_unknown = false },
} });

try expect(try ip.coerceInMemoryAllowed(arena, .anyerror_type, foo_bar_baz_set, true, builtin.target) == .ok);
Expand Down Expand Up @@ -5277,16 +5341,19 @@ test "resolvePeerTypes error sets" {
const @"error{foo}" = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{foo_name}),
.flags = .{ .has_unknown = false },
} });

const @"error{bar}" = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{bar_name}),
.flags = .{ .has_unknown = false },
} });

const @"error{bar,foo}" = try ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try ip.getStringSlice(&.{ bar_name, foo_name }),
.flags = .{ .has_unknown = false },
} });

const @"error{bar}!i32" = try ip.get(.{ .error_union_type = .{
Expand Down
2 changes: 2 additions & 0 deletions src/analyser/degibberish.zig
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ test "degibberish - error union types" {
const @"error{foo,bar,baz}" = try ip.get(.{ .error_set_type = .{
.names = try ip.getStringSlice(&.{ foo_string, bar_string, baz_string }),
.owner_decl = .none,
.flags = .{ .has_unknown = false },
} });

const @"error{foo,bar,baz}!u32" = try ip.get(.{ .error_union_type = .{
Expand All @@ -232,6 +233,7 @@ test "degibberish - error set types" {
const @"error{foo,bar,baz}" = try ip.get(.{ .error_set_type = .{
.names = try ip.getStringSlice(&.{ foo_string, bar_string, baz_string }),
.owner_decl = .none,
.flags = .{ .has_unknown = false },
} });

try std.testing.expectFmt("error set of (foo,bar,baz)", "{f}", .{fmtDegibberish(&ip, @"error{foo,bar,baz}")});
Expand Down
17 changes: 10 additions & 7 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2222,8 +2222,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error

.merge_error_sets => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
const lhs_index = try analyser.resolveErrorSetIPIndex(.of(lhs, handle)) orelse return null;
const rhs_index = try analyser.resolveErrorSetIPIndex(.of(rhs, handle)) orelse return null;
const lhs_index = try analyser.resolveErrorSetIPIndex(.of(lhs, handle)) orelse .unknown_type;
const rhs_index = try analyser.resolveErrorSetIPIndex(.of(rhs, handle)) orelse .unknown_type;
const ip_index = try analyser.ip.errorSetMerge(lhs_index, rhs_index);
return Type.fromIP(analyser, .type_type, ip_index);
},
Expand All @@ -2242,7 +2242,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error
try strings.put(analyser.gpa, index, {});
}
const names = try analyser.ip.getStringSlice(strings.keys());
const ip_index = try analyser.ip.get(.{ .error_set_type = .{ .owner_decl = .none, .names = names } });
const ip_index = try analyser.ip.get(.{ .error_set_type = .{ .owner_decl = .none, .names = names, .flags = .{ .has_unknown = false } } });
return Type.fromIP(analyser, .type_type, ip_index);
},

Expand Down Expand Up @@ -2801,10 +2801,13 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) Error
const name = offsets.identifierTokenToNameSlice(tree, name_token);
const name_index = try analyser.ip.string_pool.getOrPutString(analyser.store.io, analyser.gpa, name);

const error_set_type = try analyser.ip.get(.{ .error_set_type = .{
.owner_decl = .none,
.names = try analyser.ip.getStringSlice(&.{name_index}),
} });
const error_set_type = try analyser.ip.get(.{
.error_set_type = .{
.owner_decl = .none,
.names = try analyser.ip.getStringSlice(&.{name_index}),
.flags = .{ .has_unknown = false },
},
});
const error_value = try analyser.ip.get(.{ .error_value = .{
.ty = error_set_type,
.error_tag_name = name_index,
Expand Down
24 changes: 24 additions & 0 deletions tests/analysis/error_union.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,27 @@ const ErrorUnionUnknownError = UnknownType!u32;

const ErrorUnionUnknownPayload = Error!UnknownType;
// ^^^^^^^^^^^^^^^^^^^^^^^^ (type)(error{Foo,Bar}!(unknown type))

const ErrorSet = error{A} || error{B};
// ^^^^^^^^ (type)(error{A,B})

const ErrorSetWithUnknown = error{A} || UnknownType;
// ^^^^^^^^^^^^^^^^^^^ (type)(error{A, ... })

const ErrorSetWithUnknown2 = UnknownType || error{B};
// ^^^^^^^^^^^^^^^^^^^^ (type)(error{B, ... })

const ErrorSetWithUnknown3 = UnknownType || error{A} || error{B};
// ^^^^^^^^^^^^^^^^^^^^ (type)(error{A,B, ... })

const AllUnknown = UnknownType || UnknownType;
// ^^^^^^^^^^ (type)(error{ ... })

const AnyerrorUnknown = anyerror || UnknownType;
// ^^^^^^^^^^^^^^^ (type)(anyerror)

const UnknownAnyerror = UnknownType || anyerror;
// ^^^^^^^^^^^^^^^ (type)(anyerror)

const AnyerrorPartiallyUnknown = anyerror || error{A} || UnknownType;
// ^^^^^^^^^^^^^^^^^^^^^^^^ (type)(anyerror)
Loading