From bfada7c5144ce7f99ab92a76f65ca310440a2df9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 17:30:15 -0800 Subject: [PATCH 1/9] stage2: implement peer type resolution between error unions --- src/Sema.zig | 201 ++++++++++++++++++++++++++++++++++++++++ src/type.zig | 17 ++++ test/behavior/error.zig | 2 - 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); } From 38aae2cb7cc1dfa906336392a422153b9a345401 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 18:04:26 -0800 Subject: [PATCH 2/9] stage2: peer resolve error sets and unions, add more tests --- src/Sema.zig | 72 ++++++++++++++++++++++++++------- test/behavior/cast.zig | 90 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 14 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 04119617ff..269f726f57 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17635,20 +17635,8 @@ fn resolvePeerTypes( if (candidate_ty.eql(chosen_ty)) continue; - // If the candidate can coerce into our chosen type, we're done. - // If the chosen type can coerce into the candidate, use that. - if ((try sema.coerceInMemoryAllowed(block, chosen_ty, candidate_ty, false, target, src, src)) == .ok) { - continue; - } - if ((try sema.coerceInMemoryAllowed(block, candidate_ty, chosen_ty, false, target, src, src)) == .ok) { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } - const candidate_ty_tag = candidate_ty.zigTypeTag(); const chosen_ty_tag = chosen_ty.zigTypeTag(); - switch (candidate_ty_tag) { .NoReturn, .Undefined => continue, @@ -17780,6 +17768,51 @@ fn resolvePeerTypes( chosen_i = candidate_i + 1; 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 anything is anyerror, we use anyerror always + if (candidate_ty.isAnyError()) { + err_set_ty = candidate_ty; + continue; + } + if (err_set_ty) |ty| + if (ty.isAnyError()) continue; + + if (err_set_ty == null) { + // Error unions are lazy, we're forced to resolve now. + // Otherwise, our candidate type cause we've never seen + // error sets up to this point + if (chosen_ty_tag == .ErrorUnion) { + err_set_ty = chosen_ty.errorUnionSet(); + + if (err_set_ty.?.castTag(.error_set_inferred)) |inferred| { + try sema.resolveInferredErrorSet(inferred.data); + } + + if (err_set_ty.?.isAnyError()) continue; + } else { + err_set_ty = candidate_ty; + continue; + } + } + + // If previous is superset, keep the previous + 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 + + // Merge + err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty); + continue; }, .ErrorUnion => { if (chosen_ty_tag == .ErrorSet) { @@ -17803,14 +17836,14 @@ fn resolvePeerTypes( // 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)) { + if (!eu_set_ty.errorSetHasField(name)) { cand_is_superset = false; break; } } if (cand_is_superset) { // Swap to candidate - err_set_ty = candidate_ty; + err_set_ty = eu_set_ty; chosen = candidate; chosen_i = candidate_i + 1; continue; @@ -18085,6 +18118,17 @@ fn resolvePeerTypes( else => {}, } + // If the candidate can coerce into our chosen type, we're done. + // If the chosen type can coerce into the candidate, use that. + if ((try sema.coerceInMemoryAllowed(block, chosen_ty, candidate_ty, false, target, src, src)) == .ok) { + continue; + } + if ((try sema.coerceInMemoryAllowed(block, candidate_ty, chosen_ty, false, target, src, src)) == .ok) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + // At this point, we hit a compile error. We need to recover // the source locations. const chosen_src = candidate_srcs.resolve( diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 4ce4faa375..33f4eca8b7 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -615,6 +615,96 @@ test "peer type resolution: unreachable, error set, unreachable" { try expect(transformed_err == error.SystemResources); } +test "peer cast: error set any anyerror" { + const a: error{ One, Two } = undefined; + const b: anyerror = undefined; + try expect(@TypeOf(a, b) == anyerror); + try expect(@TypeOf(b, a) == anyerror); +} + +test "peer type resolution: error set supersets" { + const a: error{ One, Two } = undefined; + const b: error{One} = undefined; + + // A superset of B + { + const ty = @TypeOf(a, b); + const error_set_info = @typeInfo(ty); + try expect(error_set_info == .ErrorSet); + try expect(error_set_info.ErrorSet.?.len == 2); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + } + + // B superset of A + { + const ty = @TypeOf(b, a); + const error_set_info = @typeInfo(ty); + try expect(error_set_info == .ErrorSet); + try expect(error_set_info.ErrorSet.?.len == 2); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + } +} + +test "peer type resolution: disjoint error sets" { + const a: error{ One, Two } = undefined; + const b: error{Three} = undefined; + + // note: order of error set members doesn't member, may want to sort + + { + const ty = @TypeOf(a, b); + const error_set_info = @typeInfo(ty); + try expect(error_set_info == .ErrorSet); + try expect(error_set_info.ErrorSet.?.len == 3); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); + } + + { + const ty = @TypeOf(b, a); + const error_set_info = @typeInfo(ty); + try expect(error_set_info == .ErrorSet); + try expect(error_set_info.ErrorSet.?.len == 3); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); + } +} + +test "peer type resolution: error union and error set" { + const a: error{Three} = undefined; + const b: error{ One, Two }!u32 = undefined; + + // note: order of error set members doesn't member, may want to sort + + { + const ty = @TypeOf(a, b); + const info = @typeInfo(ty); + try expect(info == .ErrorUnion); + + const error_set_info = @typeInfo(info.ErrorUnion.error_set); + try expect(error_set_info.ErrorSet.?.len == 3); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); + } + + { + const ty = @TypeOf(b, a); + const info = @typeInfo(ty); + try expect(info == .ErrorUnion); + + const error_set_info = @typeInfo(info.ErrorUnion.error_set); + try expect(error_set_info.ErrorSet.?.len == 3); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); + } +} + test "peer cast *[0]T to E![]const T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO From b4ce8557884bd8f2b3fa621c3564f9d0ff789699 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 19:00:16 -0800 Subject: [PATCH 3/9] stage2: error union and non-error set/union peer cast resolution --- src/Sema.zig | 73 ++++++++++++++++++++++++++++++------------ test/behavior/cast.zig | 31 ++++++++++++++++++ 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 269f726f57..e5cad45e90 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17814,8 +17814,8 @@ fn resolvePeerTypes( err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty); continue; }, - .ErrorUnion => { - if (chosen_ty_tag == .ErrorSet) { + .ErrorUnion => switch (chosen_ty_tag) { + .ErrorSet => { if (err_set_ty.?.isAnyError()) { chosen = candidate; chosen_i = candidate_i + 1; @@ -17854,9 +17854,9 @@ fn resolvePeerTypes( chosen = candidate; chosen_i = candidate_i + 1; continue; - } + }, - if (chosen_ty_tag == .ErrorUnion) { + .ErrorUnion => { const chosen_payload_ty = chosen_ty.errorUnionPayload(); const candidate_payload_ty = candidate_ty.errorUnionPayload(); @@ -17926,30 +17926,57 @@ fn resolvePeerTypes( 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 - chosen_ty.childType().zigTypeTag() == .Array and - payload_ty.isSlice()) - { - const chosen_child_ty = chosen_ty.childType(); - const chosen_elem_ty = chosen_child_ty.elemType2(); - const candidate_elem_ty = payload_ty.elemType2(); - if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) { + .Pointer => { + const payload_ty = candidate_ty.errorUnionPayload(); + if (chosen_ty.ptrSize() == .One and + chosen_ty.childType().zigTypeTag() == .Array and + payload_ty.isSlice()) + { + const chosen_child_ty = chosen_ty.childType(); + const chosen_elem_ty = chosen_child_ty.elemType2(); + const candidate_elem_ty = payload_ty.elemType2(); + if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) { + chosen = candidate; + chosen_i = candidate_i + 1; + + convert_to_slice = false; // it already is a slice + + // If the prev pointer is const then we need to const + if (chosen_child_ty.isConstPtr()) + make_the_slice_const = true; + + continue; + } + } + }, + + else => { + // Chosen coercing into payload type + // Then merge error sets (if any) + const payload_ty = candidate_ty.errorUnionPayload(); + if ((try sema.coerceInMemoryAllowed(block, payload_ty, chosen_ty, false, target, src, src)) == .ok) { chosen = candidate; chosen_i = candidate_i + 1; - convert_to_slice = false; // it already is a slice + if (err_set_ty) |ty| { + const cand_set_ty = candidate_ty.errorUnionSet(); + if (cand_set_ty.castTag(.error_set_inferred)) |inferred| { + try sema.resolveInferredErrorSet(inferred.data); + } + if (cand_set_ty.isAnyError()) { + err_set_ty = cand_set_ty; + continue; + } + if (ty.isAnyError()) continue; - // If the prev pointer is const then we need to const - if (chosen_child_ty.isConstPtr()) - make_the_slice_const = true; + err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, cand_set_ty); + } continue; } - } + }, }, .Pointer => { if (candidate_ty.ptrSize() == .C) { @@ -18115,6 +18142,12 @@ fn resolvePeerTypes( continue; } }, + .ErrorUnion => { + const payload_ty = chosen_ty.errorUnionPayload(); + if ((try sema.coerceInMemoryAllowed(block, payload_ty, candidate_ty, false, target, src, src)) == .ok) { + continue; + } + }, else => {}, } diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 33f4eca8b7..14494a54c6 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -705,6 +705,37 @@ test "peer type resolution: error union and error set" { } } +test "peer type resolution: error union after non-error" { + const a: u32 = undefined; + const b: error{ One, Two }!u32 = undefined; + + // note: order of error set members doesn't member, may want to sort + + { + const ty = @TypeOf(a, b); + const info = @typeInfo(ty); + try expect(info == .ErrorUnion); + try expect(info.ErrorUnion.payload == u32); + + const error_set_info = @typeInfo(info.ErrorUnion.error_set); + try expect(error_set_info.ErrorSet.?.len == 2); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + } + + { + const ty = @TypeOf(b, a); + const info = @typeInfo(ty); + try expect(info == .ErrorUnion); + try expect(info.ErrorUnion.payload == u32); + + const error_set_info = @typeInfo(info.ErrorUnion.error_set); + try expect(error_set_info.ErrorSet.?.len == 2); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + } +} + test "peer cast *[0]T to E![]const T" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO From 6dae1de017f6d9e49fc99eb14c689180e08fb7fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 19:05:04 -0800 Subject: [PATCH 4/9] skip tests on native backends --- test/behavior/cast.zig | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 14494a54c6..af2eef03f1 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -591,7 +591,10 @@ test "@floatCast cast down" { } test "peer type resolution: unreachable, error set, unreachable" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO const Error = error{ FileDescriptorAlreadyPresentInSet, @@ -616,6 +619,11 @@ test "peer type resolution: unreachable, error set, unreachable" { } test "peer cast: error set any anyerror" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + const a: error{ One, Two } = undefined; const b: anyerror = undefined; try expect(@TypeOf(a, b) == anyerror); @@ -623,6 +631,11 @@ test "peer cast: error set any anyerror" { } test "peer type resolution: error set supersets" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + const a: error{ One, Two } = undefined; const b: error{One} = undefined; @@ -648,6 +661,11 @@ test "peer type resolution: error set supersets" { } test "peer type resolution: disjoint error sets" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + const a: error{ One, Two } = undefined; const b: error{Three} = undefined; @@ -675,6 +693,11 @@ test "peer type resolution: disjoint error sets" { } test "peer type resolution: error union and error set" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + const a: error{Three} = undefined; const b: error{ One, Two }!u32 = undefined; @@ -706,6 +729,11 @@ test "peer type resolution: error union and error set" { } test "peer type resolution: error union after non-error" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + const a: u32 = undefined; const b: error{ One, Two }!u32 = undefined; From 0eb3cb9e8cd263172fec797ed916aade3e5404f0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 19:13:52 -0800 Subject: [PATCH 5/9] stage2: order of error set merging should match stage1 --- src/Sema.zig | 4 ++-- test/behavior/cast.zig | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e5cad45e90..e69d0829f1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17763,7 +17763,7 @@ fn resolvePeerTypes( } // Merge errors - err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty); + err_set_ty = try candidate_ty.errorSetMerge(sema.arena, err_set_ty.?); chosen = candidate; chosen_i = candidate_i + 1; continue; @@ -17850,7 +17850,7 @@ fn resolvePeerTypes( } // Not a superset, create merged error set - err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, eu_set_ty); + err_set_ty = try eu_set_ty.errorSetMerge(sema.arena, err_set_ty.?); chosen = candidate; chosen_i = candidate_i + 1; continue; diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index af2eef03f1..9ecfaa5edb 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -669,16 +669,16 @@ test "peer type resolution: disjoint error sets" { const a: error{ One, Two } = undefined; const b: error{Three} = undefined; - // note: order of error set members doesn't member, may want to sort + // note: order of error set made to match stage1 during stage2 dev { const ty = @TypeOf(a, b); const error_set_info = @typeInfo(ty); try expect(error_set_info == .ErrorSet); try expect(error_set_info.ErrorSet.?.len == 3); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); } { @@ -686,9 +686,9 @@ test "peer type resolution: disjoint error sets" { const error_set_info = @typeInfo(ty); try expect(error_set_info == .ErrorSet); try expect(error_set_info.ErrorSet.?.len == 3); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); } } @@ -701,7 +701,7 @@ test "peer type resolution: error union and error set" { const a: error{Three} = undefined; const b: error{ One, Two }!u32 = undefined; - // note: order of error set members doesn't member, may want to sort + // note: order of error set made to match stage1 during stage2 dev { const ty = @TypeOf(a, b); @@ -710,9 +710,9 @@ test "peer type resolution: error union and error set" { const error_set_info = @typeInfo(info.ErrorUnion.error_set); try expect(error_set_info.ErrorSet.?.len == 3); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); } { @@ -737,7 +737,7 @@ test "peer type resolution: error union after non-error" { const a: u32 = undefined; const b: error{ One, Two }!u32 = undefined; - // note: order of error set members doesn't member, may want to sort + // note: order of error set made to match stage1 during stage2 dev { const ty = @TypeOf(a, b); From ea00e500457231083263302cc6972998a1a92910 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Feb 2022 19:15:32 -0800 Subject: [PATCH 6/9] stage2: skip error union test on native --- test/behavior/cast.zig | 5 +++++ test/behavior/error.zig | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 9ecfaa5edb..b66ba02daa 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -595,6 +595,7 @@ test "peer type resolution: unreachable, error set, unreachable" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const Error = error{ FileDescriptorAlreadyPresentInSet, @@ -635,6 +636,7 @@ test "peer type resolution: error set supersets" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const a: error{ One, Two } = undefined; const b: error{One} = undefined; @@ -665,6 +667,7 @@ test "peer type resolution: disjoint error sets" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const a: error{ One, Two } = undefined; const b: error{Three} = undefined; @@ -697,6 +700,7 @@ test "peer type resolution: error union and error set" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const a: error{Three} = undefined; const b: error{ One, Two }!u32 = undefined; @@ -733,6 +737,7 @@ test "peer type resolution: error union after non-error" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const a: u32 = undefined; const b: error{ One, Two }!u32 = undefined; diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 04989388e5..582a11ebc4 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -264,6 +264,12 @@ fn testErrToIntWithOnePossibleValue( } test "error union peer type resolution" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + try testErrorUnionPeerTypeResolution(1); } From 0ce2d46ff9b520ebd09f499cd2caf6e5667f5fa7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Feb 2022 09:41:03 -0800 Subject: [PATCH 7/9] stage2: error union/set peer resolution switch to "else" form of for loop --- src/Sema.zig | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e69d0829f1..b395f85d00 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17738,23 +17738,17 @@ fn resolvePeerTypes( // 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 + } else continue; - 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) { + } else { // Swap to candidate err_set_ty = candidate_ty; chosen = candidate; @@ -17801,14 +17795,11 @@ fn resolvePeerTypes( } // If previous is superset, keep the previous - 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 + } else continue; // Merge err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty); @@ -17834,14 +17825,11 @@ fn resolvePeerTypes( } // If candidate is a superset of the error type, then use it. - var cand_is_superset = true; for (err_set_ty.?.errorSetNames()) |name| { if (!eu_set_ty.errorSetHasField(name)) { - cand_is_superset = false; break; } - } - if (cand_is_superset) { + } else { // Swap to candidate err_set_ty = eu_set_ty; chosen = candidate; @@ -17901,23 +17889,17 @@ fn resolvePeerTypes( 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 + } else continue; - 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) { + } else { err_set_ty = candidate_ty; continue; } From 5e2e7675d53c4258a21d014a09f2a6fdae64b433 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 28 Feb 2022 17:40:56 -0800 Subject: [PATCH 8/9] stage2: pass generic poison up the chain in peer type resolution --- src/Sema.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index b395f85d00..6595f977b2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17612,9 +17612,8 @@ fn resolvePeerTypes( 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) + const chosen_ty_tag = try chosen_ty.zigTypeTagOrPoison(); + if (chosen_ty_tag != .ErrorSet) break :blk null; // If our chosen type is inferred, we have to resolve it now. From 4763fd1a41bc5f6bb59b5c3c079069b84f19305a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Feb 2022 23:15:58 -0700 Subject: [PATCH 9/9] Sema: clean up peer resolution of errors * Fix compile error for `zirErrorUnionType`. * Convert zirMergeErrorSets logic to call `Type.errorSetMerge`. It does not need to create a Decl as the TODO comment hinted. * Extract out a function called `resolveInferredErrorSetTy`. * Rework `resolvePeerTypes` with respect to error unions and error sets. This is a less complex implementation that passes all the same tests and uses many fewer lines of code by taking advantage of the function `coerceInMemoryAllowedErrorSets`. - Always merge error sets in the order that makes sense, even when that means `@typeInfo` incompatibility with stage1. * `Type.errorSetMerge` no longer overallocates. * Don't skip passing tests. --- src/Sema.zig | 365 ++++++++++++---------------------------- src/type.zig | 16 +- test/behavior/cast.zig | 78 ++++----- test/behavior/error.zig | 5 +- 4 files changed, 150 insertions(+), 314 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 6595f977b2..a71ddc773a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5141,15 +5141,15 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const error_union = try sema.resolveType(block, lhs_src, extra.lhs); + const error_set = try sema.resolveType(block, lhs_src, extra.lhs); const payload = try sema.resolveType(block, rhs_src, extra.rhs); - if (error_union.zigTypeTag() != .ErrorSet) { + if (error_set.zigTypeTag() != .ErrorSet) { return sema.fail(block, lhs_src, "expected error set type, found {}", .{ - error_union.elemType(), + error_set, }); } - const err_union_ty = try Module.errorUnionType(sema.arena, error_union, payload); + const err_union_ty = try Module.errorUnionType(sema.arena, error_set, payload); return sema.addType(err_union_ty); } @@ -5281,31 +5281,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr } } - // Resolve both error sets now. - const lhs_names = lhs_ty.errorSetNames(); - const rhs_names = rhs_ty.errorSetNames(); - - // TODO do we really want to create a Decl for this? - // The reason we do it right now is for memory management. - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); - - var names = Module.ErrorSet.NameMap{}; - // TODO: Guess is an upper bound, but maybe this needs to be reduced by computing the exact size first. - try names.ensureUnusedCapacity(anon_decl.arena(), @intCast(u32, lhs_names.len + rhs_names.len)); - for (lhs_names) |name| { - names.putAssumeCapacityNoClobber(name, {}); - } - for (rhs_names) |name| { - names.putAssumeCapacity(name, {}); - } - - const err_set_ty = try Type.Tag.error_set_merged.create(anon_decl.arena(), names); - const err_set_decl = try anon_decl.finish( - Type.type, - try Value.Tag.ty.create(anon_decl.arena(), err_set_ty), - ); - try sema.mod.declareDeclDependency(sema.owner_decl, err_set_decl); + const err_set_ty = try lhs_ty.errorSetMerge(sema.arena, rhs_ty); return sema.addType(err_set_ty); } @@ -6858,9 +6834,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } } - if (operand_ty.castTag(.error_set_inferred)) |inferred| { - try sema.resolveInferredErrorSet(inferred.data); - } + try sema.resolveInferredErrorSetTy(operand_ty); if (operand_ty.isAnyError()) { if (special_prong != .@"else") { @@ -10361,9 +10335,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai }; // If the error set is inferred it has to be resolved at this point - if (ty.castTag(.error_set_inferred)) |payload| { - try sema.resolveInferredErrorSet(payload.data); - } + try sema.resolveInferredErrorSetTy(ty); // Build our list of Error values // Optional value is only null if anyerror @@ -17610,32 +17582,25 @@ fn resolvePeerTypes( const target = sema.mod.getTarget(); var chosen = instructions[0]; - var err_set_ty: ?Type = blk: { - const chosen_ty = sema.typeOf(chosen); - const chosen_ty_tag = try chosen_ty.zigTypeTagOrPoison(); - if (chosen_ty_tag != .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; - }; - + // If this is non-null then it does the following thing, depending on the chosen zigTypeTag(). + // * ErrorSet: this is an override + // * ErrorUnion: this is an override of the error set only + // * other: at the end we make an ErrorUnion with the other thing and this + var err_set_ty: ?Type = null; var any_are_null = false; - var make_the_slice_const = false; + var seen_const = false; var convert_to_slice = false; var chosen_i: usize = 0; for (instructions[1..]) |candidate, candidate_i| { const candidate_ty = sema.typeOf(candidate); const chosen_ty = sema.typeOf(chosen); + + const candidate_ty_tag = try candidate_ty.zigTypeTagOrPoison(); + const chosen_ty_tag = try chosen_ty.zigTypeTagOrPoison(); + if (candidate_ty.eql(chosen_ty)) continue; - const candidate_ty_tag = candidate_ty.zigTypeTag(); - const chosen_ty_tag = chosen_ty.zigTypeTag(); switch (candidate_ty_tag) { .NoReturn, .Undefined => continue, @@ -17713,131 +17678,70 @@ 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; - } - + .ErrorSet => switch (chosen_ty_tag) { + .ErrorSet => { // If chosen is superset of candidate, keep it. // If candidate is superset of chosen, switch it. // If neither is a superset, merge errors. - for (candidate_ty.errorSetNames()) |name| { - if (!err_set_ty.?.errorSetHasField(name)) { - break; - } - } else continue; + const chosen_set_ty = err_set_ty orelse chosen_ty; - for (err_set_ty.?.errorSetNames()) |name| { - if (!candidate_ty.errorSetHasField(name)) { - break; - } - } else { - // Swap to candidate - err_set_ty = candidate_ty; + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) { + continue; + } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) { + err_set_ty = null; chosen = candidate; chosen_i = candidate_i + 1; continue; } - // Merge errors - err_set_ty = try candidate_ty.errorSetMerge(sema.arena, err_set_ty.?); - chosen = candidate; - chosen_i = candidate_i + 1; + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); continue; - } + }, + .ErrorUnion => { + const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); - // At this point, we must resolve any inferred error sets - if (candidate_ty.castTag(.error_set_inferred)) |inferred| { - try sema.resolveInferredErrorSet(inferred.data); - } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) { + continue; + } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) { + err_set_ty = candidate_ty; + continue; + } - // If anything is anyerror, we use anyerror always - if (candidate_ty.isAnyError()) { - err_set_ty = candidate_ty; + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); continue; - } - if (err_set_ty) |ty| - if (ty.isAnyError()) continue; - - if (err_set_ty == null) { - // Error unions are lazy, we're forced to resolve now. - // Otherwise, our candidate type cause we've never seen - // error sets up to this point - if (chosen_ty_tag == .ErrorUnion) { - err_set_ty = chosen_ty.errorUnionSet(); - - if (err_set_ty.?.castTag(.error_set_inferred)) |inferred| { - try sema.resolveInferredErrorSet(inferred.data); + }, + else => { + if (err_set_ty) |chosen_set_ty| { + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) { + continue; + } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) { + err_set_ty = candidate_ty; + continue; } - if (err_set_ty.?.isAnyError()) continue; + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + continue; } else { err_set_ty = candidate_ty; continue; } - } - - // If previous is superset, keep the previous - for (candidate_ty.errorSetNames()) |name| { - if (!err_set_ty.?.errorSetHasField(name)) { - break; - } - } else continue; - - // Merge - err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, candidate_ty); - continue; + }, }, .ErrorUnion => switch (chosen_ty_tag) { .ErrorSet => { - if (err_set_ty.?.isAnyError()) { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } + const chosen_set_ty = err_set_ty orelse chosen_ty; + const candidate_set_ty = candidate_ty.errorUnionSet(); - 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. - for (err_set_ty.?.errorSetNames()) |name| { - if (!eu_set_ty.errorSetHasField(name)) { - break; - } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_set_ty)) { + err_set_ty = chosen_set_ty; + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) { + err_set_ty = null; } else { - // Swap to candidate - err_set_ty = eu_set_ty; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); } - - // Not a superset, create merged error set - err_set_ty = try eu_set_ty.errorSetMerge(sema.arena, err_set_ty.?); chosen = candidate; chosen_i = candidate_i + 1; continue; @@ -17859,104 +17763,35 @@ fn resolvePeerTypes( chosen_i = candidate_i + 1; } - const chosen_set_ty = chosen_ty.errorUnionSet(); + const chosen_set_ty = err_set_ty orelse 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()) { + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_set_ty)) { err_set_ty = chosen_set_ty; - continue; - } - - if (candidate_set_ty.isAnyError()) { + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) { 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. - for (candidate_set_ty.errorSetNames()) |name| { - if (!chosen_set_ty.errorSetHasField(name)) { - break; - } - } else continue; - - for (chosen_set_ty.errorSetNames()) |name| { - if (!candidate_set_ty.errorSetHasField(name)) { - break; - } } else { - err_set_ty = candidate_ty; - continue; + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); } - - // Merge errors - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); continue; } }, - .Pointer => { - const payload_ty = candidate_ty.errorUnionPayload(); - if (chosen_ty.ptrSize() == .One and - chosen_ty.childType().zigTypeTag() == .Array and - payload_ty.isSlice()) - { - const chosen_child_ty = chosen_ty.childType(); - const chosen_elem_ty = chosen_child_ty.elemType2(); - const candidate_elem_ty = payload_ty.elemType2(); - if ((try sema.coerceInMemoryAllowed(block, candidate_elem_ty, chosen_elem_ty, false, target, src, src)) == .ok) { - chosen = candidate; - chosen_i = candidate_i + 1; - - convert_to_slice = false; // it already is a slice - - // If the prev pointer is const then we need to const - if (chosen_child_ty.isConstPtr()) - make_the_slice_const = true; - - continue; - } - } - }, - else => { - // Chosen coercing into payload type - // Then merge error sets (if any) - const payload_ty = candidate_ty.errorUnionPayload(); - if ((try sema.coerceInMemoryAllowed(block, payload_ty, chosen_ty, false, target, src, src)) == .ok) { - chosen = candidate; - chosen_i = candidate_i + 1; - - if (err_set_ty) |ty| { - const cand_set_ty = candidate_ty.errorUnionSet(); - if (cand_set_ty.castTag(.error_set_inferred)) |inferred| { - try sema.resolveInferredErrorSet(inferred.data); - } - if (cand_set_ty.isAnyError()) { - err_set_ty = cand_set_ty; - continue; - } - if (ty.isAnyError()) continue; - - err_set_ty = try err_set_ty.?.errorSetMerge(sema.arena, cand_set_ty); + if (err_set_ty) |chosen_set_ty| { + const candidate_set_ty = candidate_ty.errorUnionSet(); + if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_set_ty)) { + err_set_ty = chosen_set_ty; + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) { + err_set_ty = null; + } else { + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); } - - continue; } + seen_const = seen_const or chosen_ty.isConstPtr(); + chosen = candidate; + chosen_i = candidate_i + 1; + continue; }, }, .Pointer => { @@ -17983,7 +17818,7 @@ fn resolvePeerTypes( convert_to_slice = false; if (chosen_ty.childType().isConstPtr() and !candidate_ty.childType().isConstPtr()) - make_the_slice_const = true; + seen_const = true; continue; } @@ -17995,7 +17830,7 @@ fn resolvePeerTypes( chosen_ty.ptrSize() == .Many) { if (candidate_ty.childType().isConstPtr() and !chosen_ty.childType().isConstPtr()) - make_the_slice_const = true; + seen_const = true; continue; } @@ -18016,7 +17851,7 @@ fn resolvePeerTypes( // If the pointer is const then we need to const if (candidate_ty.childType().isConstPtr()) - make_the_slice_const = true; + seen_const = true; continue; } @@ -18039,7 +17874,7 @@ fn resolvePeerTypes( // If the prev pointer is const then we need to const if (chosen_child_ty.isConstPtr()) - make_the_slice_const = true; + seen_const = true; continue; } @@ -18075,7 +17910,7 @@ fn resolvePeerTypes( // If one of the pointers is to const data, the slice // must also be const. if (candidate_child_ty.isConstPtr() or chosen_child_ty.isConstPtr()) - make_the_slice_const = true; + seen_const = true; continue; } @@ -18186,39 +18021,47 @@ fn resolvePeerTypes( var info = chosen_ty.ptrInfo(); info.data.sentinel = chosen_child_ty.sentinel(); info.data.size = .Slice; - info.data.mutable = chosen_child_ty.isConstPtr() or make_the_slice_const; + info.data.mutable = seen_const or chosen_child_ty.isConstPtr(); info.data.pointee_type = switch (chosen_child_ty.tag()) { .array => chosen_child_ty.elemType2(), .array_u8, .array_u8_sentinel_0 => Type.initTag(.u8), else => unreachable, }; - return Type.ptr(sema.arena, target, info.data); + const new_ptr_ty = try Type.ptr(sema.arena, target, info.data); + const set_ty = err_set_ty orelse return new_ptr_ty; + return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty); } - if (make_the_slice_const) { + if (seen_const) { // turn []T => []const T - var info = chosen_ty.ptrInfo(); - info.data.mutable = false; - return Type.ptr(sema.arena, target, info.data); + switch (chosen_ty.zigTypeTag()) { + .ErrorUnion => { + const ptr_ty = chosen_ty.errorUnionPayload(); + var info = ptr_ty.ptrInfo(); + info.data.mutable = false; + const new_ptr_ty = try Type.ptr(sema.arena, target, info.data); + const set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); + return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty); + }, + .Pointer => { + var info = chosen_ty.ptrInfo(); + info.data.mutable = false; + const new_ptr_ty = try Type.ptr(sema.arena, target, info.data); + const set_ty = err_set_ty orelse return new_ptr_ty; + return try Module.errorUnionType(sema.arena, set_ty, new_ptr_ty); + }, + else => return chosen_ty, + } } 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); - }, + else => return try Module.errorUnionType(sema.arena, ty, chosen_ty), }; return chosen_ty; @@ -18507,6 +18350,12 @@ fn resolveInferredErrorSet(sema: *Sema, inferred_error_set: *Module.Fn.InferredE inferred_error_set.is_resolved = true; } +fn resolveInferredErrorSetTy(sema: *Sema, ty: Type) CompileError!void { + if (ty.castTag(.error_set_inferred)) |inferred| { + try sema.resolveInferredErrorSet(inferred.data); + } +} + fn semaStructFields( mod: *Module, struct_obj: *Module.Struct, diff --git a/src/type.zig b/src/type.zig index 34a83999b3..96b8a78dea 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4216,18 +4216,18 @@ 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)); + /// Merge lhs with rhs. + /// Asserts that lhs and rhs are both error sets and are resolved. + pub fn errorSetMerge(lhs: Type, arena: Allocator, rhs: Type) !Type { + const lhs_names = lhs.errorSetNames(); + const rhs_names = rhs.errorSetNames(); + var names: Module.ErrorSet.NameMap = .{}; + try names.ensureUnusedCapacity(arena, lhs_names.len); for (lhs_names) |name| { names.putAssumeCapacityNoClobber(name, {}); } for (rhs_names) |name| { - names.putAssumeCapacity(name, {}); + try names.put(arena, name, {}); } return try Tag.error_set_merged.create(arena, names); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index b66ba02daa..dc641b1bf4 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -591,11 +591,10 @@ test "@floatCast cast down" { } test "peer type resolution: unreachable, error set, unreachable" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const Error = error{ FileDescriptorAlreadyPresentInSet, @@ -620,11 +619,6 @@ test "peer type resolution: unreachable, error set, unreachable" { } test "peer cast: error set any anyerror" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - const a: error{ One, Two } = undefined; const b: anyerror = undefined; try expect(@TypeOf(a, b) == anyerror); @@ -632,11 +626,9 @@ test "peer cast: error set any anyerror" { } test "peer type resolution: error set supersets" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const a: error{ One, Two } = undefined; const b: error{One} = undefined; @@ -663,50 +655,52 @@ test "peer type resolution: error set supersets" { } test "peer type resolution: disjoint error sets" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage1) { + // stage1 gets the order of the error names wrong after merging the sets. + return error.SkipZigTest; + } + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const a: error{ One, Two } = undefined; const b: error{Three} = undefined; - // note: order of error set made to match stage1 during stage2 dev - { const ty = @TypeOf(a, b); const error_set_info = @typeInfo(ty); try expect(error_set_info == .ErrorSet); try expect(error_set_info.ErrorSet.?.len == 3); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); - } - - { - const ty = @TypeOf(b, a); - const error_set_info = @typeInfo(ty); - try expect(error_set_info == .ErrorSet); - try expect(error_set_info.ErrorSet.?.len == 3); try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); } + + { + const ty = @TypeOf(b, a); + const error_set_info = @typeInfo(ty); + try expect(error_set_info == .ErrorSet); + try expect(error_set_info.ErrorSet.?.len == 3); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); + } } test "peer type resolution: error union and error set" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage1) { + // stage1 gets the order of the error names wrong after merging the sets. + return error.SkipZigTest; + } + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const a: error{Three} = undefined; const b: error{ One, Two }!u32 = undefined; - // note: order of error set made to match stage1 during stage2 dev - { const ty = @TypeOf(a, b); const info = @typeInfo(ty); @@ -714,9 +708,9 @@ test "peer type resolution: error union and error set" { const error_set_info = @typeInfo(info.ErrorUnion.error_set); try expect(error_set_info.ErrorSet.?.len == 3); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "One")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "Two")); - try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "Three")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[1].name, "One")); + try expect(mem.eql(u8, error_set_info.ErrorSet.?[2].name, "Two")); } { @@ -733,17 +727,13 @@ test "peer type resolution: error union and error set" { } test "peer type resolution: error union after non-error" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const a: u32 = undefined; const b: error{ One, Two }!u32 = undefined; - // note: order of error set made to match stage1 during stage2 dev - { const ty = @TypeOf(a, b); const info = @typeInfo(ty); diff --git a/test/behavior/error.zig b/test/behavior/error.zig index 582a11ebc4..12cc3983a9 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -264,11 +264,8 @@ fn testErrToIntWithOnePossibleValue( } test "error union peer type resolution" { - if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try testErrorUnionPeerTypeResolution(1); }