Commit 55ba335e0f

Andrew Kelley <andrew@ziglang.org>
2022-03-12 03:31:29
Sema: fix resolution of inferred error sets
Introduce `Module.ensureFuncBodyAnalyzed` and corresponding `Sema` function. This mirrors `ensureDeclAnalyzed` except also waits until the function body has been semantically analyzed, meaning that inferred error sets will have been populated. Resolving error sets can now emit a "unable to resolve inferred error set" error instead of producing an incorrect error set type. Resolving error sets now calls `ensureFuncBodyAnalyzed`. Closes #11046. `coerceInMemoryAllowedErrorSets` now does a lot more work to avoid resolving an inferred error set if possible. Same with `wrapErrorUnionSet`. Inferred error set types no longer check the `func` field to determine if they are equal. That was incorrect because an inline or comptime function call produces a unique error set which has the same `*Module.Fn` value for this field. Instead we use the `*Module.Fn.InferredErrorSet` pointers to test equality of inferred error sets.
1 parent 2ee3cc4
src/Compilation.zig
@@ -15,7 +15,6 @@ const Package = @import("Package.zig");
 const link = @import("link.zig");
 const tracy = @import("tracy.zig");
 const trace = tracy.trace;
-const Liveness = @import("Liveness.zig");
 const build_options = @import("build_options");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 const glibc = @import("glibc.zig");
@@ -2702,12 +2701,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             => return,
 
             .complete, .codegen_failure_retryable => {
-                const named_frame = tracy.namedFrame("codegen_decl");
-                defer named_frame.end();
-
                 if (build_options.omit_stage2)
                     @panic("sadly stage2 is omitted from this build to save memory on the CI server");
 
+                const named_frame = tracy.namedFrame("codegen_decl");
+                defer named_frame.end();
+
                 const module = comp.bin_file.options.module.?;
                 assert(decl.has_tv);
 
@@ -2722,100 +2721,18 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
                 return;
             },
         },
-        .codegen_func => |func| switch (func.owner_decl.analysis) {
-            .unreferenced => unreachable,
-            .in_progress => unreachable,
-            .outdated => unreachable,
-
-            .file_failure,
-            .sema_failure,
-            .codegen_failure,
-            .dependency_failure,
-            .sema_failure_retryable,
-            => return,
-
-            .complete, .codegen_failure_retryable => {
-                if (build_options.omit_stage2)
-                    @panic("sadly stage2 is omitted from this build to save memory on the CI server");
-                switch (func.state) {
-                    .sema_failure, .dependency_failure => return,
-                    .queued => {},
-                    .in_progress => unreachable,
-                    .inline_only => unreachable, // don't queue work for this
-                    .success => unreachable, // don't queue it twice
-                }
-
-                const gpa = comp.gpa;
-                const module = comp.bin_file.options.module.?;
-                const decl = func.owner_decl;
-
-                var tmp_arena = std.heap.ArenaAllocator.init(gpa);
-                defer tmp_arena.deinit();
-                const sema_arena = tmp_arena.allocator();
-
-                const sema_frame = tracy.namedFrame("sema");
-                var sema_frame_ended = false;
-                errdefer if (!sema_frame_ended) sema_frame.end();
-
-                var air = module.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) {
-                    error.AnalysisFail => {
-                        if (func.state == .in_progress) {
-                            // If this decl caused the compile error, the analysis field would
-                            // be changed to indicate it was this Decl's fault. Because this
-                            // did not happen, we infer here that it was a dependency failure.
-                            func.state = .dependency_failure;
-                        }
-                        return;
-                    },
-                    error.OutOfMemory => return error.OutOfMemory,
-                };
-                defer air.deinit(gpa);
-
-                sema_frame.end();
-                sema_frame_ended = true;
-
-                if (comp.bin_file.options.emit == null) return;
-
-                const liveness_frame = tracy.namedFrame("liveness");
-                var liveness_frame_ended = false;
-                errdefer if (!liveness_frame_ended) liveness_frame.end();
-
-                log.debug("analyze liveness of {s}", .{decl.name});
-                var liveness = try Liveness.analyze(gpa, air);
-                defer liveness.deinit(gpa);
-
-                liveness_frame.end();
-                liveness_frame_ended = true;
-
-                if (builtin.mode == .Debug and comp.verbose_air) {
-                    std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
-                    @import("print_air.zig").dump(gpa, air, liveness);
-                    std.debug.print("# End Function AIR: {s}\n\n", .{decl.name});
-                }
+        .codegen_func => |func| {
+            if (build_options.omit_stage2)
+                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
 
-                const named_frame = tracy.namedFrame("codegen");
-                defer named_frame.end();
+            const named_frame = tracy.namedFrame("codegen_func");
+            defer named_frame.end();
 
-                comp.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
-                    error.OutOfMemory => return error.OutOfMemory,
-                    error.AnalysisFail => {
-                        decl.analysis = .codegen_failure;
-                        return;
-                    },
-                    else => {
-                        try module.failed_decls.ensureUnusedCapacity(gpa, 1);
-                        module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
-                            gpa,
-                            decl.srcLoc(),
-                            "unable to codegen: {s}",
-                            .{@errorName(err)},
-                        ));
-                        decl.analysis = .codegen_failure_retryable;
-                        return;
-                    },
-                };
-                return;
-            },
+            const module = comp.bin_file.options.module.?;
+            module.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.AnalysisFail => return,
+            };
         },
         .emit_h_decl => |decl| switch (decl.analysis) {
             .unreferenced => unreachable,
@@ -2831,11 +2748,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             // emit-h only requires semantic analysis of the Decl to be complete,
             // it does not depend on machine code generation to succeed.
             .codegen_failure, .codegen_failure_retryable, .complete => {
+                if (build_options.omit_stage2)
+                    @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+
                 const named_frame = tracy.namedFrame("emit_h_decl");
                 defer named_frame.end();
 
-                if (build_options.omit_stage2)
-                    @panic("sadly stage2 is omitted from this build to save memory on the CI server");
                 const gpa = comp.gpa;
                 const module = comp.bin_file.options.module.?;
                 const emit_h = module.emit_h.?;
@@ -2871,11 +2789,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             },
         },
         .analyze_decl => |decl| {
+            if (build_options.omit_stage2)
+                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+
             const named_frame = tracy.namedFrame("analyze_decl");
             defer named_frame.end();
 
-            if (build_options.omit_stage2)
-                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
             const module = comp.bin_file.options.module.?;
             module.ensureDeclAnalyzed(decl) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
@@ -2883,11 +2802,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             };
         },
         .update_embed_file => |embed_file| {
+            if (build_options.omit_stage2)
+                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+
             const named_frame = tracy.namedFrame("update_embed_file");
             defer named_frame.end();
 
-            if (build_options.omit_stage2)
-                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
             const module = comp.bin_file.options.module.?;
             module.updateEmbedFile(embed_file) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
@@ -2895,11 +2815,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             };
         },
         .update_line_number => |decl| {
+            if (build_options.omit_stage2)
+                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+
             const named_frame = tracy.namedFrame("update_line_number");
             defer named_frame.end();
 
-            if (build_options.omit_stage2)
-                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
             const gpa = comp.gpa;
             const module = comp.bin_file.options.module.?;
             comp.bin_file.updateDeclLineNumber(module, decl) catch |err| {
@@ -2914,11 +2835,12 @@ fn processOneJob(comp: *Compilation, job: Job, main_progress_node: *std.Progress
             };
         },
         .analyze_pkg => |pkg| {
+            if (build_options.omit_stage2)
+                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
+
             const named_frame = tracy.namedFrame("analyze_pkg");
             defer named_frame.end();
 
-            if (build_options.omit_stage2)
-                @panic("sadly stage2 is omitted from this build to save memory on the CI server");
             const module = comp.bin_file.options.module.?;
             module.semaPkg(pkg) catch |err| switch (err) {
                 error.CurrentWorkingDirectoryUnlinked,
src/Module.zig
@@ -3,6 +3,7 @@
 //! there is or is not any zig source code, respectively.
 
 const std = @import("std");
+const builtin = @import("builtin");
 const mem = std.mem;
 const Allocator = std.mem.Allocator;
 const ArrayListUnmanaged = std.ArrayListUnmanaged;
@@ -28,6 +29,7 @@ const AstGen = @import("AstGen.zig");
 const Sema = @import("Sema.zig");
 const target_util = @import("target.zig");
 const build_options = @import("build_options");
+const Liveness = @import("Liveness.zig");
 
 /// General-purpose allocator. Used for both temporary and long-term storage.
 gpa: Allocator,
@@ -1438,8 +1440,11 @@ pub const Fn = struct {
     is_cold: bool = false,
     is_noinline: 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.
+    /// Any inferred error sets that this function owns, both its own inferred error set and
+    /// inferred error sets of any inline/comptime functions called. Not to be confused
+    /// with inferred error sets of generic instantiations of this function, which are
+    /// *not* tracked here - they are tracked in the new `Fn` object created for the
+    /// instantiations.
     inferred_error_sets: InferredErrorSetList = .{},
 
     pub const Analysis = enum {
@@ -1457,28 +1462,29 @@ pub const Fn = struct {
     };
 
     /// 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.
+    /// 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.
+        /// 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: ErrorSet.NameMap = .{},
 
         /// 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.
+        /// 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.
+        /// 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 {
@@ -1494,8 +1500,8 @@ pub const Fn = struct {
                     try self.errors.put(gpa, name, {});
                 },
                 .error_set_inferred => {
-                    const set = err_set_ty.castTag(.error_set_inferred).?.data;
-                    try self.inferred_error_sets.put(gpa, set, {});
+                    const ies = err_set_ty.castTag(.error_set_inferred).?.data;
+                    try self.inferred_error_sets.put(gpa, ies, {});
                 },
                 .error_set_merged => {
                     const names = err_set_ty.castTag(.error_set_merged).?.data.keys();
@@ -3441,6 +3447,10 @@ pub fn mapOldZirToNew(
     }
 }
 
+/// This ensures that the Decl will have a Type and Value populated.
+/// However the resolution status of the Type may not be fully resolved.
+/// For example an inferred error set is not resolved until after `analyzeFnBody`.
+/// is called.
 pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -3533,6 +3543,87 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void {
     }
 }
 
+pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    switch (func.owner_decl.analysis) {
+        .unreferenced => unreachable,
+        .in_progress => unreachable,
+        .outdated => unreachable,
+
+        .file_failure,
+        .sema_failure,
+        .codegen_failure,
+        .dependency_failure,
+        .sema_failure_retryable,
+        => return error.AnalysisFail,
+
+        .complete, .codegen_failure_retryable => {
+            switch (func.state) {
+                .sema_failure, .dependency_failure => return error.AnalysisFail,
+                .queued => {},
+                .in_progress => unreachable,
+                .inline_only => unreachable, // don't queue work for this
+                .success => return,
+            }
+
+            const gpa = mod.gpa;
+            const decl = func.owner_decl;
+
+            var tmp_arena = std.heap.ArenaAllocator.init(gpa);
+            defer tmp_arena.deinit();
+            const sema_arena = tmp_arena.allocator();
+
+            var air = mod.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) {
+                error.AnalysisFail => {
+                    if (func.state == .in_progress) {
+                        // If this decl caused the compile error, the analysis field would
+                        // be changed to indicate it was this Decl's fault. Because this
+                        // did not happen, we infer here that it was a dependency failure.
+                        func.state = .dependency_failure;
+                    }
+                    return error.AnalysisFail;
+                },
+                error.OutOfMemory => return error.OutOfMemory,
+            };
+            defer air.deinit(gpa);
+
+            if (mod.comp.bin_file.options.emit == null) return;
+
+            log.debug("analyze liveness of {s}", .{decl.name});
+            var liveness = try Liveness.analyze(gpa, air);
+            defer liveness.deinit(gpa);
+
+            if (builtin.mode == .Debug and mod.comp.verbose_air) {
+                std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
+                @import("print_air.zig").dump(gpa, air, liveness);
+                std.debug.print("# End Function AIR: {s}\n\n", .{decl.name});
+            }
+
+            mod.comp.bin_file.updateFunc(mod, func, air, liveness) catch |err| switch (err) {
+                error.OutOfMemory => return error.OutOfMemory,
+                error.AnalysisFail => {
+                    decl.analysis = .codegen_failure;
+                    return;
+                },
+                else => {
+                    try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
+                    mod.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
+                        gpa,
+                        decl.srcLoc(),
+                        "unable to codegen: {s}",
+                        .{@errorName(err)},
+                    ));
+                    decl.analysis = .codegen_failure_retryable;
+                    return;
+                },
+            };
+            return;
+        },
+    }
+}
+
 pub fn updateEmbedFile(mod: *Module, embed_file: *EmbedFile) SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
src/Sema.zig
@@ -5346,14 +5346,14 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     }
 
     if (lhs_ty.castTag(.error_set_inferred)) |payload| {
-        try sema.resolveInferredErrorSet(payload.data);
+        try sema.resolveInferredErrorSet(block, src, payload.data);
         // isAnyError might have changed from a false negative to a true positive after resolution.
         if (lhs_ty.isAnyError()) {
             return Air.Inst.Ref.anyerror_type;
         }
     }
     if (rhs_ty.castTag(.error_set_inferred)) |payload| {
-        try sema.resolveInferredErrorSet(payload.data);
+        try sema.resolveInferredErrorSet(block, src, payload.data);
         // isAnyError might have changed from a false negative to a true positive after resolution.
         if (rhs_ty.isAnyError()) {
             return Air.Inst.Ref.anyerror_type;
@@ -6927,7 +6927,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 }
             }
 
-            try sema.resolveInferredErrorSetTy(operand_ty);
+            try sema.resolveInferredErrorSetTy(block, src, operand_ty);
 
             if (operand_ty.isAnyError()) {
                 if (special_prong != .@"else") {
@@ -10437,7 +10437,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
-            try sema.resolveInferredErrorSetTy(ty);
+            try sema.resolveInferredErrorSetTy(block, src, ty);
 
             // Build our list of Error values
             // Optional value is only null if anyerror
@@ -12627,7 +12627,7 @@ fn zirErrSetCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
     try sema.checkErrorSetType(block, operand_src, operand_ty);
 
     if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
-        try sema.resolveInferredErrorSetTy(dest_ty);
+        try sema.resolveInferredErrorSetTy(block, src, dest_ty);
 
         if (!dest_ty.isAnyError()) {
             const error_name = val.castTag(.@"error").?.data.name;
@@ -16616,7 +16616,7 @@ fn coerceInMemoryAllowed(
 
     // Error Sets
     if (dest_tag == .ErrorSet and src_tag == .ErrorSet) {
-        return try sema.coerceInMemoryAllowedErrorSets(dest_ty, src_ty);
+        return try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src);
     }
 
     // Arrays
@@ -16646,8 +16646,11 @@ fn coerceInMemoryAllowed(
 
 fn coerceInMemoryAllowedErrorSets(
     sema: *Sema,
+    block: *Block,
     dest_ty: Type,
     src_ty: Type,
+    dest_src: LazySrcLoc,
+    src_src: LazySrcLoc,
 ) !InMemoryCoercionResult {
     // Coercion to `anyerror`. Note that this check can return false negatives
     // in case the error sets did not get resolved.
@@ -16655,24 +16658,43 @@ fn coerceInMemoryAllowedErrorSets(
         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
-    // 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.func;
-            const dst_func = dst_payload.data.func;
-
-            if (src_func == dst_func or dst_payload.data.inferred_error_sets.contains(src_payload.data)) {
-                return .ok;
-            }
-            return .no_match;
+    if (dest_ty.castTag(.error_set_inferred)) |dst_payload| {
+        const dst_ies = dst_payload.data;
+        // We will make an effort to return `ok` without resolving either error set, to
+        // avoid unnecessary "unable to resolve error set" dependency loop errors.
+        switch (src_ty.tag()) {
+            .error_set_inferred => {
+                // 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
+                // of inferred error sets.
+                const src_ies = src_ty.castTag(.error_set_inferred).?.data;
+
+                if (dst_ies.inferred_error_sets.contains(src_ies)) {
+                    return .ok;
+                }
+            },
+            .error_set_single => {
+                const name = src_ty.castTag(.error_set_single).?.data;
+                if (dst_ies.errors.contains(name)) return .ok;
+            },
+            .error_set_merged => {
+                const names = src_ty.castTag(.error_set_merged).?.data.keys();
+                for (names) |name| {
+                    if (!dst_ies.errors.contains(name)) break;
+                } else return .ok;
+            },
+            .error_set => {
+                const names = src_ty.castTag(.error_set).?.data.names.keys();
+                for (names) |name| {
+                    if (!dst_ies.errors.contains(name)) break;
+                } else return .ok;
+            },
+            .anyerror => {},
+            else => unreachable,
         }
-    }
 
-    if (dest_ty.castTag(.error_set_inferred)) |payload| {
-        try sema.resolveInferredErrorSet(payload.data);
+        try sema.resolveInferredErrorSet(block, dest_src, dst_payload.data);
         // isAnyError might have changed from a false negative to a true positive after resolution.
         if (dest_ty.isAnyError()) {
             return .ok;
@@ -16683,7 +16705,7 @@ fn coerceInMemoryAllowedErrorSets(
         .error_set_inferred => {
             const src_data = src_ty.castTag(.error_set_inferred).?.data;
 
-            try sema.resolveInferredErrorSet(src_data);
+            try sema.resolveInferredErrorSet(block, src_src, 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.
@@ -17969,6 +17991,17 @@ fn ensureDeclAnalyzed(sema: *Sema, decl: *Decl) CompileError!void {
     };
 }
 
+fn ensureFuncBodyAnalyzed(sema: *Sema, func: *Module.Fn) CompileError!void {
+    sema.mod.ensureFuncBodyAnalyzed(func) catch |err| {
+        if (sema.owner_func) |owner_func| {
+            owner_func.state = .dependency_failure;
+        } else {
+            sema.owner_decl.analysis = .dependency_failure;
+        }
+        return err;
+    };
+}
+
 fn refValue(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, val: Value) !Value {
     var anon_decl = try block.startAnonDecl(src);
     defer anon_decl.deinit();
@@ -18635,10 +18668,16 @@ fn wrapErrorUnionSet(
             },
             .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;
-                if (data.errors.contains(expected_name)) break :ok;
+                const ies = dest_err_set_ty.castTag(.error_set_inferred).?.data;
+
+                // We carefully do this in an order that avoids unnecessarily
+                // resolving the destination error set type.
+                if (ies.is_anyerror) break :ok;
+                if (ies.errors.contains(expected_name)) break :ok;
+                if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) {
+                    break :ok;
+                }
+
                 return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty);
             },
             .error_set_merged => {
@@ -18794,10 +18833,10 @@ fn resolvePeerTypes(
                     // If neither is a superset, merge errors.
                     const chosen_set_ty = err_set_ty orelse chosen_ty;
 
-                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) {
+                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) {
                         continue;
                     }
-                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) {
+                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) {
                         err_set_ty = null;
                         chosen = candidate;
                         chosen_i = candidate_i + 1;
@@ -18810,10 +18849,10 @@ fn resolvePeerTypes(
                 .ErrorUnion => {
                     const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
 
-                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) {
+                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) {
                         continue;
                     }
-                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) {
+                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) {
                         err_set_ty = candidate_ty;
                         continue;
                     }
@@ -18823,10 +18862,10 @@ fn resolvePeerTypes(
                 },
                 else => {
                     if (err_set_ty) |chosen_set_ty| {
-                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_ty)) {
+                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) {
                             continue;
                         }
-                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_ty, chosen_set_ty)) {
+                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) {
                             err_set_ty = candidate_ty;
                             continue;
                         }
@@ -18844,9 +18883,9 @@ fn resolvePeerTypes(
                     const chosen_set_ty = err_set_ty orelse chosen_ty;
                     const candidate_set_ty = candidate_ty.errorUnionSet();
 
-                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_set_ty)) {
+                    if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                         err_set_ty = chosen_set_ty;
-                    } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) {
+                    } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                         err_set_ty = null;
                     } else {
                         err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
@@ -18875,9 +18914,9 @@ fn resolvePeerTypes(
                         const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet();
                         const candidate_set_ty = chosen_ty.errorUnionSet();
 
-                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(chosen_set_ty, candidate_set_ty)) {
+                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                             err_set_ty = chosen_set_ty;
-                        } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) {
+                        } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                             err_set_ty = candidate_set_ty;
                         } else {
                             err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
@@ -18889,9 +18928,9 @@ fn resolvePeerTypes(
                 else => {
                     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)) {
+                        if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) {
                             err_set_ty = chosen_set_ty;
-                        } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(candidate_set_ty, chosen_set_ty)) {
+                        } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) {
                             err_set_ty = null;
                         } else {
                             err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty);
@@ -19458,43 +19497,44 @@ 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?
+fn resolveInferredErrorSet(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    ies: *Module.Fn.InferredErrorSet,
+) CompileError!void {
+    if (ies.is_resolved) return;
 
-    if (inferred_error_set.is_resolved) {
-        return;
+    if (ies.func.state == .in_progress) {
+        return sema.fail(block, src, "unable to resolve inferred error set", .{});
     }
-    inferred_error_set.is_resolved = true;
 
-    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;
+    // To ensure that all dependencies are properly added to the set.
+    try sema.ensureFuncBodyAnalyzed(ies.func);
 
-        if (func.*.state == .in_progress) {
-            // Recursion, doesn't alter current error set, keep going.
-            continue;
-        }
+    ies.is_resolved = true;
 
-        try sema.ensureDeclAnalyzed(decl); // To ensure that all dependencies are properly added to the set.
-        try sema.resolveInferredErrorSet(other_error_set_ptr.*);
+    var it = ies.inferred_error_sets.keyIterator();
+    while (it.next()) |other_error_set_ptr| {
+        const other_ies: *Module.Fn.InferredErrorSet = other_error_set_ptr.*;
+        try sema.resolveInferredErrorSet(block, src, other_ies);
 
-        for (other_error_set_ptr.*.errors.keys()) |key| {
-            try inferred_error_set.errors.put(sema.gpa, key, {});
+        for (other_ies.errors.keys()) |key| {
+            try ies.errors.put(sema.gpa, key, {});
         }
-        if (other_error_set_ptr.*.is_anyerror)
-            inferred_error_set.is_anyerror = true;
+        if (other_ies.is_anyerror)
+            ies.is_anyerror = true;
     }
 }
 
-fn resolveInferredErrorSetTy(sema: *Sema, ty: Type) CompileError!void {
+fn resolveInferredErrorSetTy(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    ty: Type,
+) CompileError!void {
     if (ty.castTag(.error_set_inferred)) |inferred| {
-        try sema.resolveInferredErrorSet(inferred.data);
+        try sema.resolveInferredErrorSet(block, src, inferred.data);
     }
 }
 
src/type.zig
@@ -559,9 +559,9 @@ pub const Type = extern union {
             .error_set_inferred => {
                 // Inferred error sets are only equal if both are inferred
                 // and they originate from the exact same function.
-                const a_set = a.castTag(.error_set_inferred).?.data;
-                const b_set = (b.castTag(.error_set_inferred) orelse return false).data;
-                return a_set.func == b_set.func;
+                const a_ies = a.castTag(.error_set_inferred).?.data;
+                const b_ies = (b.castTag(.error_set_inferred) orelse return false).data;
+                return a_ies == b_ies;
             },
 
             .anyerror => {
@@ -983,10 +983,10 @@ pub const Type = extern union {
 
             .error_set_inferred => {
                 // inferred error sets are compared using their data pointer
-                const set = ty.castTag(.error_set_inferred).?.data;
+                const ies: *Module.Fn.InferredErrorSet = ty.castTag(.error_set_inferred).?.data;
                 std.hash.autoHash(hasher, std.builtin.TypeId.ErrorSet);
                 std.hash.autoHash(hasher, Tag.error_set_inferred);
-                std.hash.autoHash(hasher, set.func);
+                std.hash.autoHash(hasher, ies);
             },
 
             .@"opaque" => {
test/behavior/bugs/11046.zig
@@ -0,0 +1,21 @@
+const builtin = @import("builtin");
+
+fn foo() !void {
+    var a = true;
+    if (a) return error.Foo;
+    return error.Bar;
+}
+fn bar() !void {
+    try foo();
+}
+
+test "fixed" {
+    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
+
+    bar() catch |err| switch (err) {
+        error.Foo => {}, // error: expected (inferred error set of bar), found error{Foo}
+        error.Bar => {},
+    };
+}
test/behavior/array.zig
@@ -538,9 +538,6 @@ test "type coercion of anon struct literal to array" {
             try expect(arr1[1] == 56);
             try expect(arr1[2] == 54);
 
-            if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
-            if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
-
             var x2: U = .{ .a = 42 };
             const t2 = .{ x2, .{ .b = true }, .{ .c = "hello" } };
             var arr2: [3]U = t2;
test/behavior/error.zig
@@ -476,7 +476,11 @@ test "function pointer with return type that is error union with payload which i
 }
 
 test "return result loc as peer result loc in inferred error set function" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) 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 S = struct {
         fn doTheTest() !void {
test/behavior.zig
@@ -61,6 +61,7 @@ test {
     _ = @import("behavior/bugs/7250.zig");
     _ = @import("behavior/bugs/11100.zig");
     _ = @import("behavior/bugs/10970.zig");
+    _ = @import("behavior/bugs/11046.zig");
     _ = @import("behavior/call.zig");
     _ = @import("behavior/cast.zig");
     _ = @import("behavior/comptime_memory.zig");