diff --git a/src/Compilation.zig b/src/Compilation.zig index 6561f88312664801ea4dc1d217ecf85ae413c1af..0952e9f7dc05456c4a8d4e6cbc35fa17d14ef484 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4076,7 +4076,14 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { ref = refs.get(r.referencer).?; } } - @panic("referenced transitive analysis errors, but none actually emitted"); + if (comp.debugIncremental()) { + std.debug.print("skipping compiler panic to allow incremental debug server usage", .{}); + try bundle.addRootErrorMessage(.{ + .msg = try bundle.addString("compiler bug: referenced transitive analysis errors, but none actually emitted"), + }); + } else { + @panic("referenced transitive analysis errors, but none actually emitted"); + } } }; diff --git a/src/IncrementalDebugServer.zig b/src/IncrementalDebugServer.zig index 4d34812ae507191ea3799f12209d482008e14b93..20c1af1969ad117ee63acd6428f1ee0c8490f6e6 100644 --- a/src/IncrementalDebugServer.zig +++ b/src/IncrementalDebugServer.zig @@ -286,21 +286,34 @@ fn handleCommand(zcu: *Zcu, w: *Io.Writer, cmd_str: []const u8, arg_str: []const const referencer = (ref orelse break :ref "").referencer; break :ref printAnalUnit(referencer, &ref_str_buf); }; - const has_err: []const u8 = err: { - if (zcu.failed_analysis.contains(unit)) break :err "true"; - if (zcu.transitive_failed_analysis.contains(unit)) break :err "true (transitive)"; - break :err "false"; - }; try w.print( \\last update generation: {d} \\current referencer: {s} - \\has error: {s} \\ , .{ unit_info.last_update_gen, ref_str, - has_err, }); + if (zcu.failed_analysis.get(unit)) |err_msg| { + try w.print("analysis result: failure ({q})\n", .{err_msg.msg}); + } else if (zcu.transitive_failed_analysis.get(unit)) |reason| { + switch (reason) { + .astgen_error => try w.writeAll("analysis result: transitive failure (astgen error)\n"), + .dependency_loop => try w.writeAll("analysis result: transitive failure (dependency loop)\n"), + .lost_tracking => try w.writeAll("analysis result: transitive failure (lost tracking for zir inst)\n"), + .failed_unit => |other_unit| { + var buf: [32]u8 = undefined; + try w.print("analysis result: transitive failure (failed unit: {s})\n", .{printAnalUnit(other_unit, &buf)}); + }, + .func_nav_val_changed => |func_index| try w.print("analysis result: transitive failure (owner nav of func '{d}' changed value)\n", .{@backingInt(func_index)}), + } + } else { + try w.writeAll("analysis result: success\n"); + } + if (unit.unwrap() == .func) { + const nav_id = zcu.intern_pool.indexToKey(unit.unwrap().func).func.owner_nav; + try w.print("owner nav: {d}\n", .{@backingInt(nav_id)}); + } } else if (std.mem.eql(u8, cmd_str, "unit_dependencies")) { const unit = parseAnalUnit(arg_str) orelse return w.writeAll("malformed anal unit"); const unit_info = zcu.incremental_debug_state.units.get(unit) orelse return w.writeAll("unknown anal unit"); diff --git a/src/Sema.zig b/src/Sema.zig index 2181fa563b9e9dec42b1123cc3b738874df64ca7..09268824c6dd2063f6a94d836416a36375c11e42 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1472,7 +1472,7 @@ fn analyzeBodyInner( i += 1; continue; }, - .astgen_error => return error.AnalysisFail, + .astgen_error => return sema.failTransitive(.astgen_error), .float_op_result_ty => try sema.zirFloatOpResultType(block, extended), }; }, @@ -2697,13 +2697,15 @@ fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: T }); } -pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } { +pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) SemaError { @branchHint(.cold); const zcu = sema.pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; const io = comp.io; + assert(sema.err == null); + if (build_options.enable_debug_extensions and comp.debug_compile_errors) { var wip_errors: std.zig.ErrorBundle.Wip = undefined; wip_errors.init(gpa) catch @panic("out of memory"); @@ -2729,17 +2731,11 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg err_msg.reference_trace_root = sema.owner.toOptional(); - const gop = try zcu.failed_analysis.getOrPut(gpa, sema.owner); - if (gop.found_existing) { - // If there are multiple errors for the same Decl, prefer the first one added. - sema.err = null; - err_msg.destroy(gpa); - } else { - sema.err = err_msg; - gop.value_ptr.* = err_msg; - } + try zcu.failed_analysis.putNoClobber(gpa, sema.owner, err_msg); + assert(!zcu.transitive_failed_analysis.contains(sema.owner)); - return error.AnalysisFail; + sema.err = err_msg; + return error.AlreadyReported; } /// Given an ErrorMsg, modify its message and source location to the given values, turning the @@ -4745,11 +4741,14 @@ fn failWithBadMemberAccess( .@"enum" => "enum", else => unreachable, }; - if (agg_ty.typeDeclInst(zcu)) |inst| if ((inst.resolve(ip) orelse return error.AnalysisFail) == .main_struct_inst) { - return sema.fail(block, field_src, "root source file struct '{f}' has no member named '{f}'", .{ - agg_ty.fmt(pt), field_name.fmt(ip), - }); - }; + if (agg_ty.typeDeclInst(zcu)) |inst| { + const inst_index = inst.resolve(ip) orelse return sema.failTransitive(.{ .lost_tracking = inst }); + if (inst_index == .main_struct_inst) { + return sema.fail(block, field_src, "root source file struct '{f}' has no member named '{f}'", .{ + agg_ty.fmt(pt), field_name.fmt(ip), + }); + } + } return sema.fail(block, field_src, "{s} '{f}' has no member named '{f}'", .{ kw_name, agg_ty.fmt(pt), field_name.fmt(ip), @@ -5997,7 +5996,14 @@ fn lookupInNamespace( const pt = sema.pt; const zcu = pt.zcu; - try pt.ensureNamespaceUpToDate(namespace_index); + pt.ensureNamespaceUpToDate(namespace_index) catch |err| switch (err) { + error.LostZirContainerDecl => { + const namespace = zcu.namespacePtr(namespace_index); + const ns_ty: Type = .fromInterned(namespace.owner_type); + return sema.failTransitive(.{ .lost_tracking = ns_ty.typeDeclInstAllowGeneratedTag(zcu).? }); + }, + else => |e| return e, + }; const namespace = zcu.namespacePtr(namespace_index); @@ -6062,7 +6068,7 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref const stack_trace_ty = try sema.getStdLangType(block.nodeOffset(.zero), .StackTrace); const field_name = try zcu.intern_pool.getOrPutString(gpa, io, pt.tid, "index", .no_embedded_nulls); const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, LazySrcLoc.unneeded) catch |err| switch (err) { - error.AnalysisFail => @panic("std.lang.StackTrace is corrupt"), + error.AlreadyReported => @panic("std.lang.StackTrace is corrupt"), error.ComptimeReturn, error.ComptimeBreak => unreachable, error.OutOfMemory, error.Canceled => |e| return e, }; @@ -6724,7 +6730,9 @@ fn analyzeCall( const fn_nav: InternPool.Nav, const fn_zir: Zir, const fn_tracked_inst: InternPool.TrackedInst.Index, const fn_zir_inst: Zir.Inst.Index, const fn_zir_info: Zir.FnInfo = if (func_val) |f| b: { const info = ip.indexToKey(f.toIntern()).func; const nav = ip.getNav(info.owner_nav); - const resolved_func_inst = info.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail; + const resolved_func_inst = info.zir_body_inst.resolveFull(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = info.zir_body_inst }); + }; const file = zcu.fileByIndex(resolved_func_inst.file); const zir_info = file.zir.?.getFnInfo(resolved_func_inst.inst); break :b .{ nav, file.zir.?, info.zir_body_inst, resolved_func_inst.inst, zir_info }; @@ -8355,7 +8363,10 @@ fn zirFunc( const cc: std.lang.CallingConvention = if (has_body) cc: { const func_decl_nav = sema.owner.unwrap().nav_val; const fn_is_exported = exported: { - const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail; + const decl_ti = ip.getNav(func_decl_nav).analysis.?.zir_index; + const decl_inst = decl_ti.resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = decl_ti }); + }; const zir_decl = sema.code.getDeclaration(decl_inst); break :exported zir_decl.linkage == .@"export"; }; @@ -12289,10 +12300,11 @@ fn analyzeSwitchPayloadCaptureTaggedUnion( dummy_captures, .{ .override = item_srcs }, ) catch |err| switch (err) { - error.AnalysisFail => { - const msg = sema.err orelse return error.AnalysisFail; - try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{}); - return error.AnalysisFail; + error.AlreadyReported => |e| { + if (sema.err) |msg| { + try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{}); + } + return e; }, else => |e| return e, }; @@ -12328,11 +12340,12 @@ fn analyzeSwitchPayloadCaptureTaggedUnion( dummy_captures, .{ .override = item_srcs }, ) catch |err| switch (err) { - error.AnalysisFail => { - const msg = sema.err orelse return error.AnalysisFail; - try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{}); - try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{}); - return error.AnalysisFail; + error.AlreadyReported => |e| { + if (sema.err) |msg| { + try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{}); + try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{}); + } + return e; }, else => |e| return e, }; @@ -17207,7 +17220,14 @@ fn typeInfoNamespaceDecls( const ip = &zcu.intern_pool; const namespace_index = opt_namespace_index.unwrap() orelse return; - try pt.ensureNamespaceUpToDate(namespace_index); + pt.ensureNamespaceUpToDate(namespace_index) catch |err| switch (err) { + error.LostZirContainerDecl => { + const namespace = zcu.namespacePtr(namespace_index); + const ns_ty: Type = .fromInterned(namespace.owner_type); + return sema.failTransitive(.{ .lost_tracking = ns_ty.typeDeclInstAllowGeneratedTag(zcu).? }); + }, + else => |e| return e, + }; const namespace = zcu.namespacePtr(namespace_index); const gop = try seen_namespaces.getOrPut(namespace); @@ -17933,11 +17953,12 @@ fn zirUnreachable(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } // TODO Add compile error for @optimizeFor occurring too late in a scope. sema.analyzeUnreachable(block, src, true) catch |err| switch (err) { - error.AnalysisFail => { - const msg = sema.err orelse return err; - if (!mem.eql(u8, msg.msg, "runtime safety check not allowed in naked function")) return err; - try sema.errNote(src, msg, "the end of a naked function is implicitly unreachable", .{}); - return err; + error.AlreadyReported => |e| { + if (sema.err) |msg| { + if (!mem.eql(u8, msg.msg, "runtime safety check not allowed in naked function")) return err; + try sema.errNote(src, msg, "the end of a naked function is implicitly unreachable", .{}); + } + return e; }, else => |e| return e, }; @@ -18353,11 +18374,16 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const elem_ty = blk: { const air_inst = sema.resolveInst(extra.data.elem_type); - const ty = sema.analyzeAsType(block, elem_ty_src, .type, air_inst) catch |err| { - if (err == error.AnalysisFail and sema.err != null and sema.typeOf(air_inst).isSinglePointer(zcu)) { - try sema.errNote(elem_ty_src, sema.err.?, "use '.*' to dereference pointer", .{}); - } - return err; + const ty = sema.analyzeAsType(block, elem_ty_src, .type, air_inst) catch |err| switch (err) { + error.AlreadyReported => |e| { + if (sema.err) |msg| { + if (sema.typeOf(air_inst).isSinglePointer(zcu)) { + try sema.errNote(elem_ty_src, msg, "use '.*' to dereference pointer", .{}); + } + } + return e; + }, + else => |e| return e, }; assert(!ty.isGenericPoison()); break :blk ty; @@ -24909,7 +24935,10 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } else cc: { if (has_body) { const func_decl_nav = sema.owner.unwrap().nav_val; - const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail; + const func_decl_ti = ip.getNav(func_decl_nav).analysis.?.zir_index; + const func_decl_inst = func_decl_ti.resolve(&zcu.intern_pool) orelse { + return sema.failTransitive(.{ .lost_tracking = func_decl_ti }); + }; const zir_decl = sema.code.getDeclaration(func_decl_inst); if (zir_decl.linkage == .@"export") { break :cc target.cCallingConvention() orelse { @@ -30642,7 +30671,10 @@ fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.M if (pt.zcu.analysis_in_progress.contains(unit)) { return sema.failWithDependencyLoop(unit, &reason); } - try pt.ensureMemoizedStateUpToDate(stage, &reason); + pt.ensureMemoizedStateUpToDate(stage, &reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = unit }), + else => |e| return e, + }; } pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void { @@ -30678,9 +30710,15 @@ pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: switch (kind) { .type => { try zcu.ensureNavValAnalysisQueued(nav_index); - return pt.ensureNavTypeUpToDate(nav_index, &reason); + return pt.ensureNavTypeUpToDate(nav_index, &reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = anal_unit }), + else => |e| return e, + }; + }, + .fully => return pt.ensureNavValUpToDate(nav_index, &reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = anal_unit }), + else => |e| return e, }, - .fully => return pt.ensureNavValUpToDate(nav_index, &reason), } } @@ -33743,7 +33781,10 @@ fn ensureFuncIesResolved( return sema.failWithDependencyLoop(.wrap(.{ .func = func_index }), &reason); } - try pt.ensureFuncBodyUpToDate(func_index, &reason); + pt.ensureFuncBodyUpToDate(func_index, &reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .func = func_index }) }), + else => |e| return e, + }; } pub fn resolveInferredErrorSetPtr( @@ -34962,7 +35003,9 @@ pub fn setTypeName( }, .parent => wip.setName(ip, block.type_name_ctx, sema.owner.unwrap().nav_val.toOptional()), .func => { - const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse return error.AnalysisFail); + const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = ip.funcZirBodyInst(sema.func_index) }); + }); const zir_tags = sema.code.instructions.items(.tag); var aw: std.Io.Writer.Allocating = .init(gpa); @@ -35078,7 +35121,10 @@ fn zirStructDecl( }; try sema.addTypeReferenceEntry(src, ty); - try pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)); + pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)) catch |err| switch (err) { + error.LostZirContainerDecl => unreachable, // we literally just tracked it + else => |e| return e, + }; return .fromType(ty); } @@ -35151,7 +35197,10 @@ fn zirUnionDecl( }; try sema.addTypeReferenceEntry(src, ty); - try pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)); + pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)) catch |err| switch (err) { + error.LostZirContainerDecl => unreachable, // we literally just tracked it + else => |e| return e, + }; return .fromType(ty); } @@ -35203,7 +35252,10 @@ fn zirEnumDecl( }; try sema.addTypeReferenceEntry(src, ty); - try pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)); + pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)) catch |err| switch (err) { + error.LostZirContainerDecl => unreachable, // we literally just tracked it + else => |e| return e, + }; return .fromType(ty); } @@ -35252,7 +35304,10 @@ fn zirOpaqueDecl( }; try sema.addTypeReferenceEntry(src, ty); - try pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)); + pt.ensureNamespaceUpToDate(ty.getNamespaceIndex(zcu)) catch |err| switch (err) { + error.LostZirContainerDecl => unreachable, // we literally just tracked it + else => |e| return e, + }; return .fromType(ty); } @@ -35293,5 +35348,31 @@ pub fn failWithDependencyLoop( } // A dependency loop error will be reported. Mark us all as transitive failures. - return error.AnalysisFail; + return sema.failTransitive(.dependency_loop); +} + +/// Marks the owner of `sema` as having failed semantic failed *without* an error message, and +/// returns failure. This function is suitable to call when any one of the following is true: +/// +/// * `sema.owner` is guaranteed to be unreferenced on this update, for instance because it uses a +/// dead `InternPool.TrackedInst`. +/// +/// * There is guaranteed to be a compile error if this unit is referenced. In practice, this means +/// that either there is an error elsewhere in the pipeline (e.g. AstGen), or we depend on another +/// `AnalUnit` which has itself failed. +pub fn failTransitive(sema: *Sema, reason: Zcu.TransitiveFailureReason) SemaError { + assert(sema.err == null); + const zcu = sema.pt.zcu; + const unit = sema.owner; + + log.debug("transitive failure analyzing '{f}' ({t})", .{ zcu.fmtAnalUnit(unit), reason }); + + assert(!zcu.failed_analysis.contains(unit)); + try zcu.transitive_failed_analysis.putNoClobber( + zcu.comp.gpa, + unit, + if (build_options.enable_debug_extensions) reason, + ); + + return error.AlreadyReported; } diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index f807043d8ecd7bbf6c628feb877d25c92212d08c..dd8c91c244518ac23d1d5d0be6caa30861f2c4c7 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -320,7 +320,7 @@ fn failUnsupportedResultType( self: *LowerZon, ty: Type, opt_note: ?[]const u8, -) error{ AnalysisFail, OutOfMemory } { +) Zcu.SemaError { @branchHint(.cold); const sema = self.sema; const gpa = sema.gpa; @@ -338,7 +338,7 @@ fn fail( node: Zoir.Node.Index, comptime format: []const u8, args: anytype, -) error{ AnalysisFail, OutOfMemory } { +) Zcu.SemaError { @branchHint(.cold); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.nodeSrc(node), format, args); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index b3de4f8433a768a6a5c4c174fb300f1a899edf64..1e089955e59e2efcbd3b40185cab8289460c956f 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -116,7 +116,10 @@ fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *cons if (zcu.analysis_in_progress.contains(.wrap(.{ .type_layout = ty.toIntern() }))) { return sema.failWithDependencyLoop(.wrap(.{ .type_layout = ty.toIntern() }), reason); } - try pt.ensureTypeLayoutUpToDate(ty, reason); + pt.ensureTypeLayoutUpToDate(ty, reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .type_layout = ty.toIntern() }) }), + else => |e| return e, + }; }, // values, not types @@ -166,7 +169,10 @@ pub fn ensureStructDefaultsResolved(sema: *Sema, ty: Type, src: LazySrcLoc) Sema return sema.failWithDependencyLoop(.wrap(.{ .struct_defaults = ty.toIntern() }), &reason); } - try pt.ensureStructDefaultsUpToDate(ty, &reason); + pt.ensureStructDefaultsUpToDate(ty, &reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .struct_defaults = ty.toIntern() }) }), + else => |e| return e, + }; } /// Asserts that `struct_ty` is a non-packed non-tuple struct, and that `sema.owner` is that type. @@ -188,7 +194,9 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { const struct_obj = ip.loadStructType(struct_ty.toIntern()); assert(struct_obj.want_layout); - const zir_index = struct_obj.zir_index.resolve(ip) orelse return error.AnalysisFail; + const zir_index = struct_obj.zir_index.resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = struct_obj.zir_index }); + }; var block: Block = .{ .parent = null, @@ -606,7 +614,7 @@ pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void { struct_ty.assertHasLayout(zcu); const layout_unit: InternPool.AnalUnit = .wrap(.{ .type_layout = struct_ty.toIntern() }); if (zcu.failed_analysis.contains(layout_unit) or zcu.transitive_failed_analysis.contains(layout_unit)) { - return error.AnalysisFail; + return sema.failTransitive(.{ .failed_unit = layout_unit }); } const struct_obj = ip.loadStructType(struct_ty.toIntern()); @@ -656,7 +664,9 @@ fn resolveStructDefaultsInner( assert(struct_obj.field_defaults.len > 0); // We'll need to map the struct decl instruction to provide result types - const zir_index = struct_obj.zir_index.resolve(ip) orelse return error.AnalysisFail; + const zir_index = struct_obj.zir_index.resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = struct_obj.zir_index }); + }; try sema.inst_map.ensureSpaceForInstructions(gpa, &.{zir_index}); const field_types = struct_obj.field_types.get(ip); @@ -713,7 +723,9 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { const union_obj = ip.loadUnionType(union_ty.toIntern()); assert(union_obj.want_layout); - const zir_index = union_obj.zir_index.resolve(ip) orelse return error.AnalysisFail; + const zir_index = union_obj.zir_index.resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = union_obj.zir_index }); + }; var block: Block = .{ .parent = null, @@ -1212,7 +1224,9 @@ pub fn resolveEnumLayout(sema: *Sema, enum_ty: Type) CompileError!void { }; const tracked_inst = enum_obj.zir_index.unwrap() orelse maybe_parent_union_obj.?.zir_index; - const zir_index = tracked_inst.resolve(ip) orelse return error.AnalysisFail; + const zir_index = tracked_inst.resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = tracked_inst }); + }; var block: Block = .{ .parent = null, diff --git a/src/Zcu.zig b/src/Zcu.zig index 9ed2ed7dc708d3939a1baf479fdd11798415e6fb..465506eb7b41dcbcb16620c7ef84dc8b5f406ea4 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -182,7 +182,10 @@ analysis_in_progress: std.array_hash_map.Auto(AnalUnit, ?*const DependencyReason /// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. failed_analysis: std.array_hash_map.Auto(AnalUnit, *ErrorMsg) = .empty, /// This `AnalUnit` failed semantic analysis because it required analysis of another `AnalUnit` which itself failed. -transitive_failed_analysis: std.array_hash_map.Auto(AnalUnit, void) = .empty, +transitive_failed_analysis: std.array_hash_map.Auto( + AnalUnit, + if (build_options.enable_debug_extensions) TransitiveFailureReason else void, +) = .empty, /// This `Nav` succeeded analysis, but failed codegen. /// This may be a simple "value" `Nav`, or it may be a function. /// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. @@ -351,6 +354,18 @@ pub const DependencyReason = struct { type_layout_reason: Sema.type_resolution.LayoutResolveReason, }; +/// These are not required for anything, but when the compiler is built with debug extensions, we +/// store these in `Zcu.transitive_failed_analysis` and surface them in the incremental debug server +/// (see `src/IncrementalDebugServer.zig`) because they are a useful debugging aid for bugs in +/// incremental compilation. +pub const TransitiveFailureReason = union(enum) { + astgen_error, + dependency_loop, + lost_tracking: InternPool.TrackedInst.Index, + failed_unit: AnalUnit, + func_nav_val_changed: InternPool.Index, +}; + pub const IncrementalDebugState = struct { /// All container types in the ZCU, even dead ones. /// Value is the generation the type was created on. @@ -2808,13 +2823,13 @@ pub const LazySrcLoc = struct { } }; -pub const SemaError = error{ OutOfMemory, Canceled, AnalysisFail }; +pub const SemaError = error{ OutOfMemory, Canceled, AlreadyReported }; pub const CompileError = error{ OutOfMemory, /// The compilation update is no longer desired. Canceled, /// When this is returned, the compile error for the failure has already been recorded. - AnalysisFail, + AlreadyReported, /// In a comptime scope, a return instruction was encountered. This error is only seen when /// doing a comptime function call. ComptimeReturn, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index b1292e6dedb38753832091e60c5535343dfef0c2..4092f27b8b7ca16a56b412805779584b801561f9 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -320,7 +320,7 @@ pub fn update( // Zig compilation pipeline. It selects some `AnalUnit` which we know needs to be analyzed, // and analyzes it, which may in turn discover more `AnalUnit`s which we need to analyze. while (try zcu.findOutdatedToAnalyze()) |unit| { - const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { + const maybe_err: UpdateUnitError!void = switch (unit.unwrap()) { .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null), .nav_val => |nav| pt.ensureNavValUpToDate(nav, null), @@ -332,7 +332,7 @@ pub fn update( error.Canceled, => |e| return e, - error.AnalysisFail => {}, // already reported + error.AnalysisFail => {}, }; break :res pt.ensureStructDefaultsUpToDate(.fromInterned(ty), null); }, @@ -344,7 +344,7 @@ pub fn update( error.Canceled, => |e| return e, - error.AnalysisFail => {}, // already reported + error.AnalysisFail => {}, }; } } @@ -455,7 +455,7 @@ fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zc /// Ensures that `file` has up-to-date ZIR. If not, loads the ZIR cache or runs /// AstGen as needed. Also updates `file.status`. Does not assume that `file.mod` -/// is populated. Does not return `error.AnalysisFail` on AstGen failures. +/// is populated. Returns success even if the file has AstGen errors. pub fn updateFile( pt: Zcu.PerThread, file_index: Zcu.File.Index, @@ -1036,6 +1036,11 @@ pub fn ensureFilePopulated(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Alloc zcu.setFileRootType(file_index, wip.finish(ip, new_namespace_index)); } +const UpdateUnitError = Allocator.Error || Io.Cancelable || error{ + /// Semantic analysis of this `AnalUnit` failed. + AnalysisFail, +}; + /// Ensures that all memoized state on `Zcu` is up-to-date, performing re-analysis if necessary. /// Returns `error.AnalysisFail` if an analysis error is encountered; the caller is free to ignore /// this, since the error is already registered, but it must not use the value of memoized fields. @@ -1044,7 +1049,7 @@ pub fn ensureMemoizedStateUpToDate( stage: InternPool.MemoizedStateStage, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -1078,15 +1083,7 @@ pub fn ensureMemoizedStateUpToDate( const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage, reason)) |any_changed| .{ any_changed or prev_failed, false } else |err| switch (err) { - error.AnalysisFail => res: { - if (!zcu.failed_analysis.contains(unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(unit)}); - } - break :res .{ !prev_failed, true }; - }, + error.AlreadyReported => .{ !prev_failed, true }, error.OutOfMemory => { // TODO: same as for `ensureComptimeUnitUpToDate` etc return error.OutOfMemory; @@ -1154,7 +1151,7 @@ fn analyzeMemoizedState( /// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis /// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is /// free to ignore this, since the error is already registered. -pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void { +pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) UpdateUnitError!void { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -1195,15 +1192,7 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU defer unit_tracking.end(zcu); return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) { - error.AnalysisFail => { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - return error.AnalysisFail; - }, + error.AlreadyReported => return error.AnalysisFail, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. // To report the error cleanly, we need to add a message to `failed_analysis` and a @@ -1221,8 +1210,7 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU /// Re-analyzes a `ComptimeUnit`. The unit has already been determined to be out-of-date, and old /// side effects (exports/references/etc) have been dropped. If semantic analysis fails, this -/// function will return `error.AnalysisFail`, and it is the caller's reponsibility to add an entry -/// to `transitive_failed_analysis` if necessary. +/// function will return `error.AlreadyReported`. fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.CompileError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; @@ -1239,7 +1227,14 @@ fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu defer tracy_trace.end(); tracy_trace.addTextFmt("cu_id={d}", .{cu_id}); - const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse { + try zcu.transitive_failed_analysis.putNoClobber( + gpa, + anal_unit, + if (build_options.enable_debug_extensions) .{ .lost_tracking = comptime_unit.zir_index }, + ); + return error.AlreadyReported; + }; const file = zcu.fileByIndex(inst_resolved.file); const zir = file.zir.?; @@ -1314,7 +1309,7 @@ pub fn ensureTypeLayoutUpToDate( ty: Type, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const comp = zcu.comp; @@ -1399,15 +1394,7 @@ pub fn ensureTypeLayoutUpToDate( const new_failed: bool = if (result) failed: { break :failed false; } else |err| switch (err) { - error.AnalysisFail => failed: { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - break :failed true; - }, + error.AlreadyReported => true, error.OutOfMemory, error.Canceled, => |e| return e, @@ -1442,7 +1429,7 @@ pub fn ensureStructDefaultsUpToDate( ty: Type, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const comp = zcu.comp; @@ -1513,15 +1500,7 @@ pub fn ensureStructDefaultsUpToDate( const new_failed: bool = if (Sema.type_resolution.resolveStructDefaults(&sema, ty)) failed: { break :failed false; } else |err| switch (err) { - error.AnalysisFail => failed: { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - break :failed true; - }, + error.AlreadyReported => true, error.OutOfMemory, error.Canceled, => |e| return e, @@ -1547,7 +1526,7 @@ pub fn ensureNavValUpToDate( nav_id: InternPool.Nav.Index, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -1594,15 +1573,7 @@ pub fn ensureNavValUpToDate( false, }; } else |err| switch (err) { - error.AnalysisFail => res: { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - break :res .{ !prev_failed, true }; - }, + error.AlreadyReported => .{ !prev_failed, true }, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. // To report the error cleanly, we need to add a message to `failed_analysis` and a @@ -1655,7 +1626,14 @@ fn analyzeNavVal( tracy_trace.addText(old_nav.fqn.toSlice(ip)); tracy_trace.addTextFmt("nav_id={d}", .{nav_id}); - const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse { + try zcu.transitive_failed_analysis.putNoClobber( + gpa, + anal_unit, + if (build_options.enable_debug_extensions) .{ .lost_tracking = old_nav.analysis.?.zir_index }, + ); + return error.AlreadyReported; + }; const file = zcu.fileByIndex(inst_resolved.file); const zir = file.zir.?; const zir_decl = zir.getDeclaration(inst_resolved.inst); @@ -1916,7 +1894,7 @@ pub fn ensureNavTypeUpToDate( nav_id: InternPool.Nav.Index, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; @@ -1963,15 +1941,7 @@ pub fn ensureNavTypeUpToDate( false, }; } else |err| switch (err) { - error.AnalysisFail => res: { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this unit caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - break :res .{ !prev_failed, true }; - }, + error.AlreadyReported => .{ !prev_failed, true }, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. // To report the error cleanly, we need to add a message to `failed_analysis` and a @@ -2024,7 +1994,14 @@ fn analyzeNavType( tracy_trace.addText(old_nav.fqn.toSlice(ip)); tracy_trace.addTextFmt("nav_id={d}", .{nav_id}); - const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse { + try zcu.transitive_failed_analysis.putNoClobber( + gpa, + anal_unit, + if (build_options.enable_debug_extensions) .{ .lost_tracking = old_nav.analysis.?.zir_index }, + ); + return error.AlreadyReported; + }; const file = zcu.fileByIndex(inst_resolved.file); const zir = file.zir.?; @@ -2160,7 +2137,7 @@ pub fn ensureFuncBodyUpToDate( func_index: InternPool.Index, /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. reason: ?*const Zcu.DependencyReason, -) Zcu.SemaError!void { +) UpdateUnitError!void { dev.check(.sema); const zcu = pt.zcu; @@ -2204,18 +2181,10 @@ pub fn ensureFuncBodyUpToDate( const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index, reason)) |result| .{ prev_failed or result.ies_outdated, false } else |err| switch (err) { - error.AnalysisFail => res: { - if (!zcu.failed_analysis.contains(anal_unit)) { - // If this function caused the error, it would have an entry in `failed_analysis`. - // Since it does not, this must be a transitive failure. - try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); - log.debug("mark transitive analysis failure for {f}", .{zcu.fmtAnalUnit(anal_unit)}); - } - // We consider the IES to be outdated if the function previously succeeded analysis; in this case, - // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting - // a different error later (which may now be invalid). - break :res .{ !prev_failed, true }; - }, + // We consider the IES to be outdated if the function previously succeeded analysis; in this case, + // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting + // a different error later (which may now be invalid). + error.AlreadyReported => .{ !prev_failed, true }, error.OutOfMemory => { // TODO: it's unclear how to gracefully handle this. // To report the error cleanly, we need to add a message to `failed_analysis` and a @@ -3293,9 +3262,7 @@ fn analyzeFuncBodyInner( defer sema.deinit(); // Every runtime function has a dependency on the source of the Decl it originates from. - // It also depends on the value of its owner Decl. try sema.declareDependency(.{ .src_hash = decl_analysis.zir_index }); - try sema.declareDependency(.{ .nav_val = func.owner_nav }); // Make sure that the declaration `Nav` still refers to this function (or its generic owner). // This will not be the case if the incremental update has changed a function type or turned a @@ -3306,15 +3273,23 @@ fn analyzeFuncBodyInner( // If we *are* still owned by the right NAV, this analysis updates `zir_body_inst` if necessary. if (func.generic_owner == .none) { - try pt.ensureNavValUpToDate(func.owner_nav, reason); + try sema.declareDependency(.{ .nav_val = func.owner_nav }); + pt.ensureNavValUpToDate(func.owner_nav, reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = func.owner_nav }) }), + else => |e| return e, + }; if (ip.getNav(func.owner_nav).resolved.?.value != func_index) { - return error.AnalysisFail; + return sema.failTransitive(.{ .func_nav_val_changed = func_index }); } } else { const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; - try pt.ensureNavValUpToDate(go_nav, reason); + try sema.declareDependency(.{ .nav_val = go_nav }); + pt.ensureNavValUpToDate(go_nav, reason) catch |err| switch (err) { + error.AnalysisFail => return sema.failTransitive(.{ .failed_unit = .wrap(.{ .nav_val = go_nav }) }), + else => |e| return e, + }; if (ip.getNav(go_nav).resolved.?.value != func.generic_owner) { - return error.AnalysisFail; + return sema.failTransitive(.{ .func_nav_val_changed = func.generic_owner }); } } @@ -3344,7 +3319,9 @@ fn analyzeFuncBodyInner( }; defer inner_block.instructions.deinit(gpa); - const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip) orelse return error.AnalysisFail); + const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip) orelse { + return sema.failTransitive(.{ .lost_tracking = func.zirBodyInstUnordered(ip) }); + }); // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. @@ -4378,12 +4355,18 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) (Io.Cancelable | return result.index; } +const UpdateNamespaceError = Allocator.Error || Io.Cancelable || error{ + /// This namespace refers to a ZIR container declaration which no longer exists, so any code + /// referencing it is guaranteed to be unreferenced on this update. + LostZirContainerDecl, +}; + /// Given a namespace, re-scan its declarations from the type definition if they have not /// yet been re-scanned on this update. -/// If the type declaration instruction has been lost, returns `error.AnalysisFail`. +/// If the type declaration instruction has been lost, returns `error.LostZirContainerDecl`. /// This will effectively short-circuit the caller, which will be semantic analysis of a /// guaranteed-unreferenced `AnalUnit`, to trigger a transitive analysis error. -pub fn ensureNamespaceUpToDate(pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index) Zcu.SemaError!void { +pub fn ensureNamespaceUpToDate(pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index) UpdateNamespaceError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const namespace = zcu.namespacePtr(namespace_index); @@ -4410,7 +4393,7 @@ pub fn ensureNamespaceUpToDate(pt: Zcu.PerThread, namespace_index: Zcu.Namespace // Namespace outdated -- re-scan the type if necessary. - const inst_info = key.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const inst_info = key.zir_index.resolveFull(ip) orelse return error.LostZirContainerDecl; const file = zcu.fileByIndex(inst_info.file); const zir = &file.zir.?; diff --git a/test/incremental/temporary_analysis_error_in_generic_signature b/test/incremental/temporary_analysis_error_in_generic_signature new file mode 100644 index 0000000000000000000000000000000000000000..d8db72050b3af1164a312e1e7a1ed1f89bfc3dae --- /dev/null +++ b/test/incremental/temporary_analysis_error_in_generic_signature @@ -0,0 +1,46 @@ +#update=initial version +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().Foo { + return .{ .x = x }; +} +const std = @import("std"); +#expect_stdout="Z\n" + +#update=change generic signature to use non-existent member +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().FooAlias { + return .{ .x = x }; +} +const std = @import("std"); +#expect_error=main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias' +#expect_error=main.zig:1:1: note: struct declared here + +#update=add that member, fixing the error +#file=main.zig +//! The original repro here depends on re-analysis order, which depends on +//! declaration order, so this exact declaration order must be used. +const Foo = struct { x: u8 }; +const FooAlias = Foo; +pub fn main(init: std.process.Init) !void { + const c = bar('Z').x; + try std.Io.File.stdout().writeStreamingAll(init.io, &.{ c, '\n' }); +} +fn bar(comptime x: u8) @This().FooAlias { + return .{ .x = x }; +} +const std = @import("std"); +#expect_stdout="Z\n"