diff options
| author | Robin Voetter <robin@voetter.nl> | 2021-12-17 04:39:09 +0100 |
|---|---|---|
| committer | Robin Voetter <robin@voetter.nl> | 2021-12-21 01:41:50 +0100 |
| commit | 9d6c45f6979543607a7064be7155afa409be956a (patch) | |
| tree | c92d89093e998cdca6fbe3c546e290ea00248366 /src | |
| parent | a2958a4ede0af4b4559eeb142c0400ae640db63e (diff) | |
| download | zig-9d6c45f6979543607a7064be7155afa409be956a.tar.gz zig-9d6c45f6979543607a7064be7155afa409be956a.zip | |
stage2: inferred error set coercion
Diffstat (limited to 'src')
| -rw-r--r-- | src/Module.zig | 15 | ||||
| -rw-r--r-- | src/Sema.zig | 179 | ||||
| -rw-r--r-- | src/type.zig | 31 |
3 files changed, 183 insertions, 42 deletions
diff --git a/src/Module.zig b/src/Module.zig index 7031dc20a5..4f9f8307db 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1239,14 +1239,17 @@ pub const Fn = struct { /// When the inferred error set is fully resolved, this map contains all the errors that the function might return. errors: std.StringHashMapUnmanaged(void) = .{}, - /// Other functions with inferred error sets which the inferred error set of this - /// function should include. - functions: std.AutoHashMapUnmanaged(*Fn, void) = .{}, + /// Other inferred error sets which this inferred error set should include. + inferred_error_sets: std.AutoHashMapUnmanaged(*InferredErrorSet, void) = .{}, /// Whether the function returned anyerror. This is true if either of the dependent functions /// returns anyerror. is_anyerror: bool = false, + /// Whether this error set is already fully resolved. If true, resolving can skip resolving any dependents + /// of this inferred error set. + is_resolved: bool = false, + pub fn addErrorSet(self: *InferredErrorSet, gpa: Allocator, err_set_ty: Type) !void { switch (err_set_ty.tag()) { .error_set => { @@ -1260,8 +1263,8 @@ pub const Fn = struct { try self.errors.put(gpa, name, {}); }, .error_set_inferred => { - const dependent_func = err_set_ty.castTag(.error_set_inferred).?.data.func; - try self.functions.put(gpa, dependent_func, {}); + const set = err_set_ty.castTag(.error_set_inferred).?.data; + try self.inferred_error_sets.put(gpa, set, {}); }, .error_set_merged => { const names = err_set_ty.castTag(.error_set_merged).?.data.keys(); @@ -1285,7 +1288,7 @@ pub const Fn = struct { while (it) |node| { const next = node.next; node.data.errors.deinit(gpa); - node.data.functions.deinit(gpa); + node.data.inferred_error_sets.deinit(gpa); gpa.destroy(node); it = next; } diff --git a/src/Sema.zig b/src/Sema.zig index f23ffe24c0..a82a56c253 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5207,9 +5207,6 @@ fn funcCommon( .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), .rbrace_column = @truncate(u16, src_locs.columns >> 16), - .inferred_error_sets = .{ - .first = maybe_inferred_error_set_node, - }, }; if (maybe_inferred_error_set_node) |node| { new_func.inferred_error_sets.prepend(node); @@ -12193,7 +12190,7 @@ fn coerce( const arena = sema.arena; const target = sema.mod.getTarget(); - const in_memory_result = coerceInMemoryAllowed(dest_ty, inst_ty, false, target); + const in_memory_result = try sema.coerceInMemoryAllowed(dest_ty, inst_ty, false, target); if (in_memory_result == .ok) { if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { // Keep the comptime Value representation; take the new type. @@ -12252,7 +12249,7 @@ fn coerce( if (inst_ty.isConstPtr() and dest_is_mut) break :single_item; if (inst_ty.isVolatilePtr() and !dest_info.@"volatile") break :single_item; if (inst_ty.ptrAddressSpace() != dest_info.@"addrspace") break :single_item; - switch (coerceInMemoryAllowed(array_elem_ty, ptr_elem_ty, dest_is_mut, target)) { + switch (try sema.coerceInMemoryAllowed(array_elem_ty, ptr_elem_ty, dest_is_mut, target)) { .ok => {}, .no_match => break :single_item, } @@ -12271,7 +12268,7 @@ fn coerce( if (inst_ty.ptrAddressSpace() != dest_info.@"addrspace") break :src_array_ptr; const dst_elem_type = dest_info.pointee_type; - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, target)) { + switch (try sema.coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, target)) { .ok => {}, .no_match => break :src_array_ptr, } @@ -12310,7 +12307,7 @@ fn coerce( const src_elem_ty = inst_ty.childType(); const dest_is_mut = dest_info.mutable; const dst_elem_type = dest_info.pointee_type; - switch (coerceInMemoryAllowed(dst_elem_type, src_elem_ty, dest_is_mut, target)) { + switch (try sema.coerceInMemoryAllowed(dst_elem_type, src_elem_ty, dest_is_mut, target)) { .ok => {}, .no_match => break :src_c_ptr, } @@ -12453,7 +12450,13 @@ const InMemoryCoercionResult = enum { /// * sentinel-terminated pointers can coerce into `[*]` /// TODO improve this function to report recursive compile errors like it does in stage1. /// look at the function types_match_const_cast_only -fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult { +fn coerceInMemoryAllowed( + sema: *Sema, + dest_ty: Type, + src_ty: Type, + dest_is_mut: bool, + target: std.Target +) CompileError!InMemoryCoercionResult { if (dest_ty.eql(src_ty)) return .ok; @@ -12462,32 +12465,35 @@ fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target: var src_buf: Type.Payload.ElemType = undefined; if (dest_ty.ptrOrOptionalPtrTy(&dest_buf)) |dest_ptr_ty| { if (src_ty.ptrOrOptionalPtrTy(&src_buf)) |src_ptr_ty| { - return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ptr_ty, src_ptr_ty, dest_is_mut, target); + return try sema.coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ptr_ty, src_ptr_ty, dest_is_mut, target); } } // Slices if (dest_ty.isSlice() and src_ty.isSlice()) { - return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target); + return try sema.coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target); } + const dest_tag = dest_ty.zigTypeTag(); + const src_tag = src_ty.zigTypeTag(); + // Functions - if (dest_ty.zigTypeTag() == .Fn and src_ty.zigTypeTag() == .Fn) { - return coerceInMemoryAllowedFns(dest_ty, src_ty, target); + if (dest_tag == .Fn and src_tag == .Fn) { + return try sema.coerceInMemoryAllowedFns(dest_ty, src_ty, target); } // Error Unions - if (dest_ty.zigTypeTag() == .ErrorUnion and src_ty.zigTypeTag() == .ErrorUnion) { - const child = coerceInMemoryAllowed(dest_ty.errorUnionPayload(), src_ty.errorUnionPayload(), dest_is_mut, target); + if (dest_tag == .ErrorUnion and src_tag == .ErrorUnion) { + const child = try sema.coerceInMemoryAllowed(dest_ty.errorUnionPayload(), src_ty.errorUnionPayload(), dest_is_mut, target); if (child == .no_match) { return child; } - return coerceInMemoryAllowed(dest_ty.errorUnionSet(), src_ty.errorUnionSet(), dest_is_mut, target); + return try sema.coerceInMemoryAllowed(dest_ty.errorUnionSet(), src_ty.errorUnionSet(), dest_is_mut, target); } // Error Sets - if (dest_ty.zigTypeTag() == .ErrorSet and src_ty.zigTypeTag() == .ErrorSet) { - return coerceInMemoryAllowedErrorSets(dest_ty, src_ty); + if (dest_tag == .ErrorSet and src_tag == .ErrorSet) { + return try sema.coerceInMemoryAllowedErrorSets(dest_ty, src_ty); } // TODO: arrays @@ -12498,14 +12504,16 @@ fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target: } fn coerceInMemoryAllowedErrorSets( + sema: *Sema, dest_ty: Type, src_ty: Type, -) InMemoryCoercionResult { - // Coercion to `anyerror`. Note that this check can return false positives +) !InMemoryCoercionResult { + // Coercion to `anyerror`. Note that this check can return false negatives // in case the error sets did not get resolved. if (dest_ty.isAnyError()) { return .ok; } + // If both are inferred error sets of functions, and // the dest includes the source function, the coercion is OK. // This check is important because it works without forcing a full resolution @@ -12515,21 +12523,85 @@ fn coerceInMemoryAllowedErrorSets( const src_func = src_payload.data.func; const dst_func = dst_payload.data.func; - if (src_func == dst_func or dst_payload.data.functions.contains(src_func)) { + if (src_func == dst_func or dst_payload.data.inferred_error_sets.contains(src_payload.data)) { return .ok; } + return .no_match; } } - // TODO full error set resolution and compare sets by names. + if (dest_ty.castTag(.error_set_inferred)) |payload| { + try sema.resolveInferredErrorSet(payload.data); + // isAnyError might have changed from a false negative to a true positive after resolution. + if (dest_ty.isAnyError()) { + return .ok; + } + } + + switch (src_ty.tag()) { + .error_set_inferred => { + const src_data = src_ty.castTag(.error_set_inferred).?.data; + + try sema.resolveInferredErrorSet(src_data); + // src anyerror status might have changed after the resolution. + if (src_ty.isAnyError()) { + // dest_ty.isAnyError() == true is already checked for at this point. + return .no_match; + } + + var it = src_data.errors.keyIterator(); + while (it.next()) |name_ptr| { + if (!dest_ty.errorSetHasField(name_ptr.*)) { + return .no_match; + } + } + + return .ok; + }, + .error_set_single => { + const name = src_ty.castTag(.error_set_single).?.data; + if (dest_ty.errorSetHasField(name)) { + return .ok; + } + }, + .error_set_merged => { + const names = src_ty.castTag(.error_set_merged).?.data.keys(); + for (names) |name| { + if (!dest_ty.errorSetHasField(name)) { + return .no_match; + } + } + + return .ok; + }, + .error_set => { + const names = src_ty.castTag(.error_set).?.data.names.keys(); + for (names) |name| { + if (!dest_ty.errorSetHasField(name)) { + return .no_match; + } + } + + return .ok; + }, + .anyerror => switch (dest_ty.tag()) { + .error_set_inferred => return .no_match, // Caught by dest.isAnyError() above. + .error_set_single, .error_set_merged, .error_set => {}, + .anyerror => unreachable, // Filtered out above. + else => unreachable, + }, + else => unreachable, + } + return .no_match; } fn coerceInMemoryAllowedFns( + sema: *Sema, dest_ty: Type, src_ty: Type, target: std.Target, -) InMemoryCoercionResult { +) !InMemoryCoercionResult { const dest_info = dest_ty.fnInfo(); const src_info = src_ty.fnInfo(); @@ -12542,7 +12614,7 @@ fn coerceInMemoryAllowedFns( } if (!src_info.return_type.isNoReturn()) { - const rt = coerceInMemoryAllowed(dest_info.return_type, src_info.return_type, false, target); + const rt = try sema.coerceInMemoryAllowed(dest_info.return_type, src_info.return_type, false, target); if (rt == .no_match) { return rt; } @@ -12562,7 +12634,7 @@ fn coerceInMemoryAllowedFns( // TODO: nolias // Note: Cast direction is reversed here. - const param = coerceInMemoryAllowed(src_param_ty, dest_param_ty, false, target); + const param = try sema.coerceInMemoryAllowed(src_param_ty, dest_param_ty, false, target); if (param == .no_match) { return param; } @@ -12576,17 +12648,18 @@ fn coerceInMemoryAllowedFns( } fn coerceInMemoryAllowedPtrs( + sema: *Sema, dest_ty: Type, src_ty: Type, dest_ptr_ty: Type, src_ptr_ty: Type, dest_is_mut: bool, target: std.Target, -) InMemoryCoercionResult { +) !InMemoryCoercionResult { const dest_info = dest_ptr_ty.ptrInfo().data; const src_info = src_ptr_ty.ptrInfo().data; - const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); + const child = try sema.coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); if (child == .no_match) { return child; } @@ -13307,7 +13380,7 @@ fn coerceVectorInMemory( const target = sema.mod.getTarget(); const dest_elem_ty = dest_ty.childType(); const inst_elem_ty = inst_ty.childType(); - const in_memory_result = coerceInMemoryAllowed(dest_elem_ty, inst_elem_ty, false, target); + const in_memory_result = try sema.coerceInMemoryAllowed(dest_elem_ty, inst_elem_ty, false, target); if (in_memory_result != .ok) { // TODO recursive error notes for coerceInMemoryAllowed failure return sema.fail(block, inst_src, "expected {}, found {}", .{ dest_ty, inst_ty }); @@ -13910,11 +13983,11 @@ fn wrapErrorUnion( } }, .error_set_inferred => ok: { + const expected_name = val.castTag(.@"error").?.data.name; const data = dest_err_set_ty.castTag(.error_set_inferred).?.data; + try sema.resolveInferredErrorSet(data); if (data.is_anyerror) break :ok; - const expected_name = val.castTag(.@"error").?.data.name; if (data.errors.contains(expected_name)) break :ok; - // TODO error set resolution here before emitting a compile error return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty); }, else => unreachable, @@ -14059,12 +14132,12 @@ fn resolvePeerTypes( .Optional => { var opt_child_buf: Type.Payload.ElemType = undefined; const opt_child_ty = candidate_ty.optionalChild(&opt_child_buf); - if (coerceInMemoryAllowed(opt_child_ty, chosen_ty, false, target) == .ok) { + if ((try sema.coerceInMemoryAllowed(opt_child_ty, chosen_ty, false, target)) == .ok) { chosen = candidate; chosen_i = candidate_i + 1; continue; } - if (coerceInMemoryAllowed(chosen_ty, opt_child_ty, false, target) == .ok) { + if ((try sema.coerceInMemoryAllowed(chosen_ty, opt_child_ty, false, target)) == .ok) { any_are_null = true; continue; } @@ -14087,10 +14160,10 @@ fn resolvePeerTypes( .Optional => { var opt_child_buf: Type.Payload.ElemType = undefined; const opt_child_ty = chosen_ty.optionalChild(&opt_child_buf); - if (coerceInMemoryAllowed(opt_child_ty, candidate_ty, false, target) == .ok) { + if ((try sema.coerceInMemoryAllowed(opt_child_ty, candidate_ty, false, target)) == .ok) { continue; } - if (coerceInMemoryAllowed(candidate_ty, opt_child_ty, false, target) == .ok) { + if ((try sema.coerceInMemoryAllowed(candidate_ty, opt_child_ty, false, target)) == .ok) { any_are_null = true; chosen = candidate; chosen_i = candidate_i + 1; @@ -14256,6 +14329,42 @@ fn resolveBuiltinTypeFields( return sema.resolveTypeFields(block, src, resolved_ty); } +fn resolveInferredErrorSet(sema: *Sema, inferred_error_set: *Module.Fn.InferredErrorSet) CompileError!void { + // Ensuring that a particular decl is analyzed does not neccesarily mean that + // it's error set is inferred, so traverse all of them to get the complete + // picture. + // Note: We want to skip re-resolving the current function, as recursion + // doesn't change the error set. We can just check for state == .in_progress for this. + // TODO: Is that correct? + + if (inferred_error_set.is_resolved) { + return; + } + + var it = inferred_error_set.inferred_error_sets.keyIterator(); + while (it.next()) |other_error_set_ptr| { + const func = other_error_set_ptr.*.func; + const decl = func.*.owner_decl; + + if (func.*.state == .in_progress) { + // Recursion, doesn't alter current error set, keep going. + continue; + } + + try sema.ensureDeclAnalyzed(decl); // To ensure that all dependencies are properly added to the set. + try sema.resolveInferredErrorSet(other_error_set_ptr.*); + + var error_it = other_error_set_ptr.*.errors.keyIterator(); + while (error_it.next()) |entry| { + try inferred_error_set.errors.put(sema.gpa, entry.*, {}); + } + if (other_error_set_ptr.*.is_anyerror) + inferred_error_set.is_anyerror = true; + } + + inferred_error_set.is_resolved = true; +} + fn semaStructFields( mod: *Module, struct_obj: *Module.Struct, @@ -15218,8 +15327,8 @@ fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr // We have a Value that lines up in virtual memory exactly with what we want to load. // If the Type is in-memory coercable to `load_ty`, it may be returned without modifications. const coerce_in_mem_ok = - coerceInMemoryAllowed(load_ty, parent.ty, false, target) == .ok or - coerceInMemoryAllowed(parent.ty, load_ty, false, target) == .ok; + (try sema.coerceInMemoryAllowed(load_ty, parent.ty, false, target)) == .ok or + (try sema.coerceInMemoryAllowed(parent.ty, load_ty, false, target)) == .ok; if (coerce_in_mem_ok) { if (parent.is_mutable) { // The decl whose value we are obtaining here may be overwritten with diff --git a/src/type.zig b/src/type.zig index 3360477a03..a81bd3ed32 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1203,7 +1203,7 @@ pub const Type = extern union { return writer.writeAll(std.mem.sliceTo(error_set.owner_decl.name, 0)); }, .error_set_inferred => { - const func = ty.castTag(.error_set_inferred).?.data; + const func = ty.castTag(.error_set_inferred).?.data.func; return writer.print("(inferred error set of {s})", .{func.owner_decl.name}); }, .error_set_merged => { @@ -2874,6 +2874,35 @@ pub const Type = extern union { }; } + /// Returns whether ty, which must be an error set, includes an error `name`. + /// Might return a false negative if `ty` is an inferred error set and not fully + /// resolved yet. + pub fn errorSetHasField(ty: Type, name: []const u8) bool { + if (ty.isAnyError()) { + return true; + } + + switch (ty.tag()) { + .error_set_single => { + const data = ty.castTag(.error_set_single).?.data; + return std.mem.eql(u8, data, name); + }, + .error_set_inferred => { + const data = ty.castTag(.error_set_inferred).?.data; + return data.errors.contains(name); + }, + .error_set_merged => { + const data = ty.castTag(.error_set_merged).?.data; + return data.contains(name); + }, + .error_set => { + const data = ty.castTag(.error_set).?.data; + return data.names.contains(name); + }, + else => unreachable, + } + } + /// Asserts the type is an array or vector. pub fn arrayLen(ty: Type) u64 { return switch (ty.tag()) { |
