diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index e050203cc..e5a1ec197 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -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 { @@ -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; @@ -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, @@ -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, @@ -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| { @@ -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) { + 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; + 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. @@ -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), @@ -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); @@ -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 }); @@ -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); @@ -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 = .{ diff --git a/src/analyser/degibberish.zig b/src/analyser/degibberish.zig index 3daf9a122..e653eedd4 100644 --- a/src/analyser/degibberish.zig +++ b/src/analyser/degibberish.zig @@ -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 = .{ @@ -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}")}); diff --git a/src/analysis.zig b/src/analysis.zig index e18b438d8..9f3787655 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -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); }, @@ -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); }, @@ -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, diff --git a/tests/analysis/error_union.zig b/tests/analysis/error_union.zig index dbfc3dba9..6197ad7de 100644 --- a/tests/analysis/error_union.zig +++ b/tests/analysis/error_union.zig @@ -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)