From a2958a4ede0af4b4559eeb142c0400ae640db63e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 17 Dec 2021 03:40:48 +0100 Subject: [PATCH] stage2: allow multiple inferred error sets per Fn This allows the inferred error set of comptime and inline invocations to be resolved separately from the inferred error set of the runtime version or other comptime/inline invocations. --- src/Module.zig | 112 +++++++++++++++++++++++++++------------------- src/Sema.zig | 46 ++++++++++++------- src/codegen/c.zig | 2 +- src/type.zig | 4 +- 4 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index b5d856efd072580045665a602b23c753d82c6da0..7031dc20a53d348cb4f1b20bd38dda5d1508d649 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1207,23 +1207,9 @@ pub const Fn = struct { is_cold: bool = false, is_noinline: bool = false, - /// These fields are used to keep track of any dependencies related to functions - /// that return inferred error sets. It's values are not used when the function - /// does not return an inferred error set. - inferred_error_set: struct { - /// All currently known errors that this function returns. This includes direct additions - /// via `return error.Foo;`, and possibly also errors that are returned from any dependent functions. - /// 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) = .{}, - - /// Whether the function returned anyerror. This is true if either of the dependent functions - /// returns anyerror. - is_anyerror: bool = false, - } = .{}, + /// Any inferred error sets that this function owns, both it's own inferred error set and + /// inferred error sets of any inline/comptime functions called. + inferred_error_sets: InferredErrorSetList = .{}, pub const Analysis = enum { queued, @@ -1239,37 +1225,69 @@ pub const Fn = struct { success, }; + /// This struct is used to keep track of any dependencies related to functions instances + /// that return inferred error sets. Note that a function may be associated to multiple different error sets, + /// for example an inferred error set which this function returns, but also any inferred error sets + /// of called inline or comptime functions. + pub const InferredErrorSet = struct { + /// The function from which this error set originates. + /// Note: may be the function itself. + func: *Fn, + + /// All currently known errors that this error set contains. This includes direct additions + /// via `return error.Foo;`, and possibly also errors that are returned from any dependent functions. + /// 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) = .{}, + + /// Whether the function returned anyerror. This is true if either of the dependent functions + /// returns anyerror. + is_anyerror: bool = false, + + pub fn addErrorSet(self: *InferredErrorSet, gpa: Allocator, err_set_ty: Type) !void { + switch (err_set_ty.tag()) { + .error_set => { + const names = err_set_ty.castTag(.error_set).?.data.names.keys(); + for (names) |name| { + try self.errors.put(gpa, name, {}); + } + }, + .error_set_single => { + const name = err_set_ty.castTag(.error_set_single).?.data; + 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, {}); + }, + .error_set_merged => { + const names = err_set_ty.castTag(.error_set_merged).?.data.keys(); + for (names) |name| { + try self.errors.put(gpa, name, {}); + } + }, + .anyerror => { + self.is_anyerror = true; + }, + else => unreachable, + } + } + }; + + pub const InferredErrorSetList = std.SinglyLinkedList(InferredErrorSet); + pub const InferredErrorSetListNode = InferredErrorSetList.Node; + pub fn deinit(func: *Fn, gpa: Allocator) void { - func.inferred_error_set.errors.deinit(gpa); - func.inferred_error_set.functions.deinit(gpa); - } - - pub fn addErrorSet(func: *Fn, gpa: Allocator, err_set_ty: Type) !void { - switch (err_set_ty.tag()) { - .error_set => { - const names = err_set_ty.castTag(.error_set).?.data.names.keys(); - for (names) |name| { - try func.inferred_error_set.errors.put(gpa, name, {}); - } - }, - .error_set_single => { - const name = err_set_ty.castTag(.error_set_single).?.data; - try func.inferred_error_set.errors.put(gpa, name, {}); - }, - .error_set_inferred => { - const dependent_func = err_set_ty.castTag(.error_set_inferred).?.data; - try func.inferred_error_set.functions.put(gpa, dependent_func, {}); - }, - .error_set_merged => { - const names = err_set_ty.castTag(.error_set_merged).?.data.keys(); - for (names) |name| { - try func.inferred_error_set.errors.put(gpa, name, {}); - } - }, - .anyerror => { - func.inferred_error_set.is_anyerror = true; - }, - else => unreachable, + var it = func.inferred_error_sets.first; + while (it) |node| { + const next = node.next; + node.data.errors.deinit(gpa); + node.data.functions.deinit(gpa); + gpa.destroy(node); + it = next; } } }; diff --git a/src/Sema.zig b/src/Sema.zig index 30c59f9efabc1427a8eade09fd829fc51f8f2741..f23ffe24c0f918c941f0bc87902ada20a2a9e0b4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3896,11 +3896,12 @@ fn analyzeCall( const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); // If the function has an inferred error set, `bare_return_type` is the payload type only. const fn_ret_ty = blk: { - // TODO instead of reusing the function's inferred error set, this code should - // create a temporary error set which is used for the comptime/inline function - // call alone, independent from the runtime instantiation. - if (func_ty_info.return_type.castTag(.error_union)) |payload| { - const error_set_ty = payload.data.error_set; + if (func_ty_info.return_type.tag() == .error_union) { + const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode); + node.data = .{ .func = module_fn }; + parent_func.?.inferred_error_sets.prepend(node); + + const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data); break :blk try Type.Tag.error_union.create(sema.arena, .{ .error_set = error_set_ty, .payload = bare_return_type, @@ -5066,6 +5067,10 @@ fn funcCommon( }; errdefer if (body_inst != 0) sema.gpa.destroy(new_func); + var maybe_inferred_error_set_node: ?*Module.Fn.InferredErrorSetListNode = null; + errdefer if (maybe_inferred_error_set_node) |node| sema.gpa.destroy(node); + // Note: no need to errdefer since this will still be in its default state at the end of the function. + const fn_ty: Type = fn_ty: { // Hot path for some common function types. // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. @@ -5107,7 +5112,11 @@ fn funcCommon( const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison) bare_return_type else blk: { - const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, new_func); + const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode); + node.data = .{ .func = new_func }; + maybe_inferred_error_set_node = node; + + const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data); break :blk try Type.Tag.error_union.create(sema.arena, .{ .error_set = error_set_ty, .payload = bare_return_type, @@ -5198,7 +5207,14 @@ 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); + } + maybe_inferred_error_set_node = null; fn_payload.* = .{ .base = .{ .tag = .function }, .data = new_func, @@ -9204,14 +9220,14 @@ fn analyzeRet( // add the error tag to the inferred error set of the in-scope function, so // that the coercion below works correctly. if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { - if (sema.fn_ret_ty.errorUnionSet().tag() == .error_set_inferred) { + if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| { const op_ty = sema.typeOf(uncasted_operand); switch (op_ty.zigTypeTag()) { .ErrorSet => { - try sema.func.?.addErrorSet(sema.gpa, op_ty); + try payload.data.addErrorSet(sema.gpa, op_ty); }, .ErrorUnion => { - try sema.func.?.addErrorSet(sema.gpa, op_ty.errorUnionSet()); + try payload.data.addErrorSet(sema.gpa, op_ty.errorUnionSet()); }, else => {}, } @@ -12496,10 +12512,10 @@ fn coerceInMemoryAllowedErrorSets( // of inferred error sets. if (src_ty.castTag(.error_set_inferred)) |src_payload| { if (dest_ty.castTag(.error_set_inferred)) |dst_payload| { - const src_func = src_payload.data; - const dst_func = dst_payload.data; + const src_func = src_payload.data.func; + const dst_func = dst_payload.data.func; - if (src_func == dst_func or dst_func.inferred_error_set.functions.contains(src_func)) { + if (src_func == dst_func or dst_payload.data.functions.contains(src_func)) { return .ok; } } @@ -13894,10 +13910,10 @@ fn wrapErrorUnion( } }, .error_set_inferred => ok: { - const func = dest_err_set_ty.castTag(.error_set_inferred).?.data; - if (func.inferred_error_set.is_anyerror) break :ok; + const data = dest_err_set_ty.castTag(.error_set_inferred).?.data; + if (data.is_anyerror) break :ok; const expected_name = val.castTag(.@"error").?.data.name; - if (func.inferred_error_set.errors.contains(expected_name)) break :ok; + 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); }, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 8babcb9a8339c6c9fef375bf0d96f249957f9d98..f54ae7f76de123d35e44a779e40e3b44957fb4ab 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -722,7 +722,7 @@ pub const DeclGen = struct { try bw.writeAll(" payload; uint16_t error; } "); const name_index = buffer.items.len; if (err_set_type.castTag(.error_set_inferred)) |inf_err_set_payload| { - const func = inf_err_set_payload.data; + const func = inf_err_set_payload.data.func; try bw.writeAll("zig_E_"); try dg.renderDeclName(func.owner_decl, bw); try bw.writeAll(";\n"); diff --git a/src/type.zig b/src/type.zig index 298df2cffce89412d97b6a8a450636d903cb8080..3360477a03a0b8f9e6801ad39e2cd8552b090400 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2869,7 +2869,7 @@ pub const Type = extern union { pub fn isAnyError(ty: Type) bool { return switch (ty.tag()) { .anyerror => true, - .error_set_inferred => ty.castTag(.error_set_inferred).?.data.inferred_error_set.is_anyerror, + .error_set_inferred => ty.castTag(.error_set_inferred).?.data.is_anyerror, else => false, }; } @@ -4156,7 +4156,7 @@ pub const Type = extern union { pub const base_tag = Tag.error_set_inferred; base: Payload = Payload{ .tag = base_tag }, - data: *Module.Fn, + data: *Module.Fn.InferredErrorSet, }; pub const Pointer = struct { -- 2.54.0