aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Hashimoto <mitchell.hashimoto@gmail.com>2022-02-27 17:30:15 -0800
committerAndrew Kelley <andrew@ziglang.org>2022-02-28 20:42:13 -0700
commitbfada7c5144ce7f99ab92a76f65ca310440a2df9 (patch)
treecbce207709d897d0a64583f40e33ea82d0c07fd0
parentd5131e91eba9324eda3a2ae47eb2aa4530c87e83 (diff)
downloadzig-bfada7c5144ce7f99ab92a76f65ca310440a2df9.tar.gz
zig-bfada7c5144ce7f99ab92a76f65ca310440a2df9.zip
stage2: implement peer type resolution between error unions
-rw-r--r--src/Sema.zig201
-rw-r--r--src/type.zig17
-rw-r--r--test/behavior/error.zig2
3 files changed, 218 insertions, 2 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index 6ef4798da6..04119617ff 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -17610,6 +17610,21 @@ fn resolvePeerTypes(
const target = sema.mod.getTarget();
var chosen = instructions[0];
+ var err_set_ty: ?Type = blk: {
+ const chosen_ty = sema.typeOf(chosen);
+
+ // TODO: is this the right handling of generic poison?
+ if (chosen_ty.tag() == .generic_poison or chosen_ty.zigTypeTag() != .ErrorSet)
+ break :blk null;
+
+ // If our chosen type is inferred, we have to resolve it now.
+ if (chosen_ty.castTag(.error_set_inferred)) |inferred| {
+ try sema.resolveInferredErrorSet(inferred.data);
+ }
+
+ break :blk chosen_ty;
+ };
+
var any_are_null = false;
var make_the_slice_const = false;
var convert_to_slice = false;
@@ -17711,7 +17726,175 @@ fn resolvePeerTypes(
},
else => {},
},
+ .ErrorSet => {
+ if (chosen_ty_tag == .ErrorSet) {
+ assert(err_set_ty != null);
+
+ // If chosen type is anyerror, then we can use the prev type
+ if (err_set_ty.?.isAnyError()) continue;
+
+ // At this point, we must resolve any inferred error sets
+ if (candidate_ty.castTag(.error_set_inferred)) |inferred| {
+ try sema.resolveInferredErrorSet(inferred.data);
+ }
+
+ // If candidate is anyerror then we use it because it
+ // is trivially a supserset of previous error set
+ if (candidate_ty.isAnyError()) {
+ err_set_ty = candidate_ty;
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ // If chosen is superset of candidate, keep it.
+ // If candidate is superset of chosen, switch it.
+ // If neither is a superset, merge errors.
+ var prev_is_superset = true;
+ for (candidate_ty.errorSetNames()) |name| {
+ if (!err_set_ty.?.errorSetHasField(name)) {
+ prev_is_superset = false;
+ break;
+ }
+ }
+ if (prev_is_superset) continue; // use previous
+
+ var cand_is_superset = true;
+ for (err_set_ty.?.errorSetNames()) |name| {
+ if (!candidate_ty.errorSetHasField(name)) {
+ cand_is_superset = false;
+ break;
+ }
+ }
+ if (cand_is_superset) {
+ // Swap to candidate
+ err_set_ty = candidate_ty;
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ // Merge errors
+ err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty);
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+ },
.ErrorUnion => {
+ if (chosen_ty_tag == .ErrorSet) {
+ if (err_set_ty.?.isAnyError()) {
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ const eu_set_ty = candidate_ty.errorUnionSet();
+ if (eu_set_ty.castTag(.error_set_inferred)) |inferred| {
+ try sema.resolveInferredErrorSet(inferred.data);
+ }
+ if (eu_set_ty.isAnyError()) {
+ err_set_ty = eu_set_ty;
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ // If candidate is a superset of the error type, then use it.
+ var cand_is_superset = true;
+ for (err_set_ty.?.errorSetNames()) |name| {
+ if (!candidate_ty.errorSetHasField(name)) {
+ cand_is_superset = false;
+ break;
+ }
+ }
+ if (cand_is_superset) {
+ // Swap to candidate
+ err_set_ty = candidate_ty;
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ // Not a superset, create merged error set
+ err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, eu_set_ty);
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ continue;
+ }
+
+ if (chosen_ty_tag == .ErrorUnion) {
+ const chosen_payload_ty = chosen_ty.errorUnionPayload();
+ const candidate_payload_ty = candidate_ty.errorUnionPayload();
+
+ const coerce_chosen = (try sema.coerceInMemoryAllowed(block, chosen_payload_ty, candidate_payload_ty, false, target, src, src)) == .ok;
+ const coerce_candidate = (try sema.coerceInMemoryAllowed(block, candidate_payload_ty, chosen_payload_ty, false, target, src, src)) == .ok;
+
+ if (coerce_chosen or coerce_candidate) {
+ // If we can coerce to the candidate, we switch to that
+ // type. This is the same logic as the bare (non-union)
+ // coercion check we do at the top of this func.
+ if (coerce_candidate) {
+ chosen = candidate;
+ chosen_i = candidate_i + 1;
+ }
+
+ const chosen_set_ty = chosen_ty.errorUnionSet();
+ const candidate_set_ty = chosen_ty.errorUnionSet();
+
+ // If our error sets match already, then we are done.
+ if (chosen_set_ty.eql(candidate_set_ty)) continue;
+
+ // They don't match, so we need to figure out if we
+ // need to merge them, use the superset, etc. This
+ // requires resolution.
+ if (chosen_set_ty.castTag(.error_set_inferred)) |inferred| {
+ try sema.resolveInferredErrorSet(inferred.data);
+ }
+ if (candidate_set_ty.castTag(.error_set_inferred)) |inferred| {
+ try sema.resolveInferredErrorSet(inferred.data);
+ }
+
+ if (chosen_set_ty.isAnyError()) {
+ err_set_ty = chosen_set_ty;
+ continue;
+ }
+
+ if (candidate_set_ty.isAnyError()) {
+ err_set_ty = candidate_set_ty;
+ continue;
+ }
+
+ if (err_set_ty == null) err_set_ty = chosen_set_ty;
+
+ // If the previous error set type is a superset, we're done.
+ var prev_is_superset = true;
+ for (candidate_set_ty.errorSetNames()) |name| {
+ if (!chosen_set_ty.errorSetHasField(name)) {
+ prev_is_superset = false;
+ break;
+ }
+ }
+ if (prev_is_superset) continue; // use previous
+
+ var cand_is_superset = true;
+ for (chosen_set_ty.errorSetNames()) |name| {
+ if (!candidate_set_ty.errorSetHasField(name)) {
+ cand_is_superset = false;
+ break;
+ }
+ }
+ if (cand_is_superset) {
+ err_set_ty = candidate_ty;
+ continue;
+ }
+
+ // Merge errors
+ err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty);
+ continue;
+ }
+ }
+
const payload_ty = candidate_ty.errorUnionPayload();
if (chosen_ty_tag == .Pointer and
chosen_ty.ptrSize() == .One and
@@ -17962,6 +18145,24 @@ fn resolvePeerTypes(
return Type.ptr(sema.arena, target, info.data);
}
+ if (err_set_ty) |ty| switch (chosen_ty.zigTypeTag()) {
+ .ErrorSet => return ty,
+
+ .ErrorUnion => {
+ const payload_ty = chosen_ty.errorUnionPayload();
+ return try Module.errorUnionType(sema.arena, ty, payload_ty);
+ },
+
+ .ComptimeInt, .ComptimeFloat => return sema.fail(block, src, "unable to make error union out of number literal", .{}),
+
+ .Null => return sema.fail(block, src, "unable to make error union out of null literal", .{}),
+
+ else => {
+ // Create error union of our error set and the chosen type
+ return try Module.errorUnionType(sema.arena, ty, chosen_ty);
+ },
+ };
+
return chosen_ty;
}
diff --git a/src/type.zig b/src/type.zig
index dbb5eb4ba3..34a83999b3 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -4216,6 +4216,23 @@ pub const Type = extern union {
};
}
+ /// Merge ty with ty2.
+ /// Asserts that ty and ty2 are both error sets and are resolved.
+ pub fn errorSetMerge(ty: Type, arena: Allocator, ty2: Type) !Type {
+ const lhs_names = ty.errorSetNames();
+ const rhs_names = ty2.errorSetNames();
+ var names = Module.ErrorSet.NameMap{};
+ try names.ensureUnusedCapacity(arena, @intCast(u32, lhs_names.len + rhs_names.len));
+ for (lhs_names) |name| {
+ names.putAssumeCapacityNoClobber(name, {});
+ }
+ for (rhs_names) |name| {
+ names.putAssumeCapacity(name, {});
+ }
+
+ return try Tag.error_set_merged.create(arena, names);
+ }
+
pub fn enumFields(ty: Type) Module.EnumFull.NameMap {
return switch (ty.tag()) {
.enum_full, .enum_nonexhaustive => ty.cast(Payload.EnumFull).?.data.fields,
diff --git a/test/behavior/error.zig b/test/behavior/error.zig
index ab58874c34..04989388e5 100644
--- a/test/behavior/error.zig
+++ b/test/behavior/error.zig
@@ -264,8 +264,6 @@ fn testErrToIntWithOnePossibleValue(
}
test "error union peer type resolution" {
- if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
try testErrorUnionPeerTypeResolution(1);
}