diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 64aafd110e..ef3cd3d783 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -243,12 +243,14 @@ fn renderErrorMessage( } try t.setColor(.reset); if (src.data.source_line != 0 and options.include_source_line) { + try w.splatByteAll(' ', indent); const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { '\t' => try w.writeByte(' '), else => try w.writeByte(b), }; try w.writeByte('\n'); + try w.splatByteAll(' ', indent); // TODO basic unicode code point monospace width const before_caret = src.data.span_main - src.data.span_start; // -1 since span.main includes the caret @@ -267,11 +269,13 @@ fn renderErrorMessage( if (src.data.reference_trace_len > 0 and options.include_reference_trace) { try t.setColor(.reset); try t.setColor(.dim); + try w.splatByteAll(' ', indent); try w.print("referenced by:\n", .{}); var ref_index = src.end; for (0..src.data.reference_trace_len) |_| { const ref_trace = eb.extraData(ReferenceTrace, ref_index); ref_index = ref_trace.end; + try w.splatByteAll(' ', indent); if (ref_trace.data.src_loc != .none) { const ref_src = eb.getSourceLocation(ref_trace.data.src_loc); try w.print(" {s}: {s}:{d}:{d}\n", .{ diff --git a/src/Compilation.zig b/src/Compilation.zig index 8b863c3022..59fa186d82 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4189,6 +4189,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { } } } + try zcu.addDependencyLoopErrors(&bundle); for (zcu.failed_codegen.values()) |error_msg| { try addModuleErrorMsg(zcu, &bundle, error_msg.*, false); } @@ -4208,7 +4209,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { .notes_len = 1, }); const notes_start = try bundle.reserveNotes(1); - bundle.extra.items[notes_start] = @intFromEnum(try bundle.addErrorMessage(.{ + bundle.extra.items[notes_start] = @intFromEnum(bundle.addErrorMessageAssumeCapacity(.{ .msg = try bundle.printString("use '--error-limit {d}' to increase limit", .{ actual_error_count, }), @@ -4230,10 +4231,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { .notes_len = 2, }); const notes_start = try bundle.reserveNotes(2); - bundle.extra.items[notes_start + 0] = @intFromEnum(try bundle.addErrorMessage(.{ + bundle.extra.items[notes_start + 0] = @intFromEnum(bundle.addErrorMessageAssumeCapacity(.{ .msg = try bundle.addString("run 'zig libc -h' to learn about libc installations"), })); - bundle.extra.items[notes_start + 1] = @intFromEnum(try bundle.addErrorMessage(.{ + bundle.extra.items[notes_start + 1] = @intFromEnum(bundle.addErrorMessageAssumeCapacity(.{ .msg = try bundle.addString("run 'zig targets' to see the targets for which zig can always provide libc"), })); } @@ -4428,7 +4429,6 @@ pub fn addModuleErrorMsg( already_added_error: bool, ) Allocator.Error!void { const gpa = eb.gpa; - const ip = &zcu.intern_pool; const err_src_loc = module_err_msg.src_loc.upgrade(zcu); const err_source = err_src_loc.file_scope.getSource(zcu) catch |err| { return unableToLoadZcuFile(zcu, eb, err_src_loc.file_scope, err); @@ -4441,66 +4441,12 @@ pub fn addModuleErrorMsg( var ref_traces: std.ArrayList(ErrorBundle.ReferenceTrace) = .empty; defer ref_traces.deinit(gpa); - rt: { - const rt_root = module_err_msg.reference_trace_root.unwrap() orelse break :rt; - const max_references = zcu.comp.reference_trace orelse refs: { - if (already_added_error) break :rt; + if (module_err_msg.reference_trace_root.unwrap()) |root| { + const frame_limit: u32 = zcu.comp.reference_trace orelse refs: { + if (already_added_error) break :refs 0; break :refs default_reference_trace_len; }; - - const all_references = try zcu.resolveReferences(); - - var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .empty; - defer seen.deinit(gpa); - - var referenced_by = rt_root; - while (all_references.get(referenced_by)) |maybe_ref| { - const ref = maybe_ref orelse break; - const gop = try seen.getOrPut(gpa, ref.referencer); - if (gop.found_existing) break; - if (ref_traces.items.len < max_references) { - var last_call_src = ref.src; - var opt_inline_frame = ref.inline_frame; - while (opt_inline_frame.unwrap()) |inline_frame| { - const f = inline_frame.ptr(zcu).*; - const func_nav = ip.indexToKey(f.callee).func.owner_nav; - const func_name = ip.getNav(func_nav).name.toSlice(ip); - addReferenceTraceFrame(zcu, eb, &ref_traces, func_name, last_call_src, true) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.AlreadyReported => { - // An incomplete reference trace isn't the end of the world; just cut it off. - break :rt; - }, - }; - last_call_src = f.call_src; - opt_inline_frame = f.parent; - } - const root_name: ?[]const u8 = switch (ref.referencer.unwrap()) { - .@"comptime" => "comptime", - .nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip), - .type_layout => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip), - .memoized_state => null, - }; - if (root_name) |n| { - addReferenceTraceFrame(zcu, eb, &ref_traces, n, last_call_src, false) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.AlreadyReported => { - // An incomplete reference trace isn't the end of the world; just cut it off. - break :rt; - }, - }; - } - } - referenced_by = ref.referencer; - } - - if (seen.count() > ref_traces.items.len) { - try ref_traces.append(gpa, .{ - .decl_name = @intCast(seen.count() - ref_traces.items.len), - .src_loc = .none, - }); - } + try zcu.populateReferenceTrace(root, frame_limit, eb, &ref_traces); } const src_loc = try eb.addSourceLocation(.{ @@ -4565,43 +4511,10 @@ pub fn addModuleErrorMsg( const notes_start = try eb.reserveNotes(notes_len); for (notes_start.., notes.keys()) |i, note| { - eb.extra.items[i] = @intFromEnum(try eb.addErrorMessage(note)); + eb.extra.items[i] = @intFromEnum(eb.addErrorMessageAssumeCapacity(note)); } } -fn addReferenceTraceFrame( - zcu: *Zcu, - eb: *ErrorBundle.Wip, - ref_traces: *std.ArrayList(ErrorBundle.ReferenceTrace), - name: []const u8, - lazy_src: Zcu.LazySrcLoc, - inlined: bool, -) error{ OutOfMemory, AlreadyReported }!void { - const gpa = zcu.gpa; - const src = lazy_src.upgrade(zcu); - const source = src.file_scope.getSource(zcu) catch |err| { - try unableToLoadZcuFile(zcu, eb, src.file_scope, err); - return error.AlreadyReported; - }; - const span = src.span(zcu) catch |err| { - try unableToLoadZcuFile(zcu, eb, src.file_scope, err); - return error.AlreadyReported; - }; - const loc = std.zig.findLineColumn(source, span.main); - try ref_traces.append(gpa, .{ - .decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }), - .src_loc = try eb.addSourceLocation(.{ - .src_path = try eb.printString("{f}", .{src.file_scope.path.fmt(zcu.comp)}), - .span_start = span.start, - .span_main = span.main, - .span_end = span.end, - .line = @intCast(loc.line), - .column = @intCast(loc.column), - .source_line = 0, - }), - }); -} - fn addWholeFileError( zcu: *Zcu, eb: *ErrorBundle.Wip, @@ -5243,11 +5156,11 @@ fn processOneJob(tid: Zcu.PerThread.Id, comp: *Compilation, job: Job) JobError!v const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), - .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav), - .nav_val => |nav| pt.ensureNavValUpToDate(nav), - .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty)), - .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage), - .func => |func| pt.ensureFuncBodyUpToDate(func), + .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null), + .nav_val => |nav| pt.ensureNavValUpToDate(nav, null), + .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null), + .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null), + .func => |func| pt.ensureFuncBodyUpToDate(func, null), }; maybe_err catch |err| switch (err) { error.OutOfMemory => |e| return e, diff --git a/src/Sema.zig b/src/Sema.zig index 799da305ed..cbfe6421db 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3260,7 +3260,7 @@ fn zirAllocExtended( } else .none; if (small.has_type) { - try sema.ensureLayoutResolved(var_ty, ty_src); + try sema.ensureLayoutResolved(var_ty, var_src, if (small.is_const) .constant else .variable); if (block.isComptime() or small.is_comptime or var_ty.comptimeOnly(zcu)) { return sema.analyzeComptimeAlloc(block, var_src, var_ty, alignment); } @@ -3324,7 +3324,7 @@ fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node }); const var_src = block.nodeOffset(inst_data.src_node); const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); - try sema.ensureLayoutResolved(var_ty, ty_src); + try sema.ensureLayoutResolved(var_ty, var_src, .variable); return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none); } @@ -3758,7 +3758,7 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const var_src = block.nodeOffset(inst_data.src_node); const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); - try sema.ensureLayoutResolved(var_ty, ty_src); + try sema.ensureLayoutResolved(var_ty, var_src, .constant); if (block.isComptime() or var_ty.comptimeOnly(zcu)) { return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none); } @@ -3790,7 +3790,7 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const var_src = block.nodeOffset(inst_data.src_node); const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); - try sema.ensureLayoutResolved(var_ty, ty_src); + try sema.ensureLayoutResolved(var_ty, var_src, .variable); if (block.isComptime()) { return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none); } @@ -4148,7 +4148,7 @@ fn zirOptEuBasePtrInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compile const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const ptr = sema.resolveInst(un_node.operand); const src = block.nodeOffset(un_node.src_node); - try sema.ensureLayoutResolved(sema.typeOf(ptr).childType(sema.pt.zcu), src); + try sema.ensureLayoutResolved(sema.typeOf(ptr).childType(sema.pt.zcu), src, .init); return sema.optEuBasePtrInit(block, ptr, src); } @@ -4651,7 +4651,7 @@ fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr } const elem_ty = operand_ty.childType(zcu); - try sema.ensureLayoutResolved(elem_ty, src); + try sema.ensureLayoutResolved(elem_ty, src, .ptr_access); const need_comptime = switch (elem_ty.classify(zcu)) { .no_possible_value => return sema.fail(block, src, "cannot load {s} type '{f}'", .{ @@ -7037,7 +7037,7 @@ fn analyzeCall( break :ret_ty full_ty; }; - try sema.ensureLayoutResolved(resolved_ret_ty, func_ret_ty_src); + try sema.ensureLayoutResolved(resolved_ret_ty, func_ret_ty_src, .return_type); // If we've discovered after evaluating arguments that a generic function instantiation is // comptime-only, then we can mark the block as comptime *now*. @@ -7134,7 +7134,6 @@ fn analyzeCall( .generic_owner = func_val.?.toIntern(), .comptime_args = comptime_args, }); - try sema.ensureLayoutResolved(.fromInterned(ip.typeOf(func_instance)), call_src); if (zcu.comp.debugIncremental()) { const nav = ip.indexToKey(func_instance).func.owner_nav; const gop = try zcu.incremental_debug_state.navs.getOrPut(gpa, nav); @@ -7169,7 +7168,7 @@ fn analyzeCall( }; try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + runtime_args.len); - const maybe_opv = try block.addInst(.{ + const call_ref = try block.addInst(.{ .tag = call_tag, .data = .{ .pl_op = .{ .operand = runtime_func, @@ -7180,8 +7179,10 @@ fn analyzeCall( }); sema.appendRefsAssumeCapacity(runtime_args); + const actual_ret_ty = sema.typeOf(call_ref); + if (ensure_result_used) { - try sema.ensureResultUsed(block, sema.typeOf(maybe_opv), call_src); + try sema.ensureResultUsed(block, actual_ret_ty, call_src); } if (call_tag == .call_always_tail) { @@ -7191,28 +7192,31 @@ fn analyzeCall( .pointer => func_or_ptr_ty.childType(zcu), else => unreachable, }; - return sema.handleTailCall(block, call_src, runtime_func_ty, maybe_opv); + return sema.handleTailCall(block, call_src, runtime_func_ty, call_ref); } - if (resolved_ret_ty.isNoReturn(zcu)) { - const want_check = c: { - if (!block.wantSafety()) break :c false; - if (func_val != null) break :c false; - break :c true; - }; - if (want_check) { - try sema.safetyPanic(block, call_src, .noreturn_returned); - } else { - _ = try block.addNoOp(.unreach); - } - return .unreachable_value; - } - - try sema.ensureLayoutResolved(sema.typeOf(maybe_opv), func_ret_ty_src); - if (try sema.typeOf(maybe_opv).onePossibleValue(pt)) |opv| { - return .fromValue(opv); - } else { - return maybe_opv; + switch (actual_ret_ty.classify(zcu)) { + .no_possible_value => { + const want_check = c: { + if (!block.wantSafety()) break :c false; + if (func_val != null) break :c false; + break :c true; + }; + if (want_check) { + try sema.safetyPanic(block, call_src, .noreturn_returned); + } else { + _ = try block.addNoOp(.unreach); + } + return .unreachable_value; + }, + .one_possible_value => { + return .fromValue((try actual_ret_ty.onePossibleValue(pt)).?); + }, + .runtime => { + return call_ref; + }, + .partially_comptime => unreachable, + .fully_comptime => unreachable, } } @@ -7279,11 +7283,6 @@ fn analyzeCall( } } - // We're about to do an inline call; if the return type expression was generic, the return type - // may not be resolved yet. It's correct to resolve it because the function is going to return a - // value of this type. - try sema.ensureLayoutResolved(resolved_ret_ty, func_ret_ty_src); - // For an inline call, we depend on the source code of the whole function definition. try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index }); @@ -8051,7 +8050,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (dest_ty.zigTypeTag(zcu) != .@"enum") { return sema.fail(block, src, "expected enum, found '{f}'", .{dest_ty.fmt(pt)}); } - try sema.ensureLayoutResolved(dest_ty, src); + try sema.ensureLayoutResolved(dest_ty, src, .init); _ = try sema.checkIntType(block, operand_src, operand_ty); if (sema.resolveValue(operand)) |int_val| { @@ -8114,7 +8113,7 @@ fn zirOptionalPayloadPtr( const ptr_ty = sema.typeOf(optional_ptr); assert(ptr_ty.zigTypeTag(sema.pt.zcu) == .pointer); - try sema.ensureLayoutResolved(ptr_ty.childType(sema.pt.zcu), src); + try sema.ensureLayoutResolved(ptr_ty.childType(sema.pt.zcu), src, .ptr_access); return sema.analyzeOptionalPayloadPtr(block, src, optional_ptr, safety_check, false); } @@ -8311,7 +8310,7 @@ fn zirErrUnionPayloadPtr( const ptr_ty = sema.typeOf(operand); assert(ptr_ty.zigTypeTag(sema.pt.zcu) == .pointer); - try sema.ensureLayoutResolved(ptr_ty.childType(sema.pt.zcu), src); + try sema.ensureLayoutResolved(ptr_ty.childType(sema.pt.zcu), src, .ptr_access); return sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); } @@ -9020,7 +9019,6 @@ fn funcCommon( const io = comp.io; const ip = &zcu.intern_pool; - const src = block.nodeOffset(src_node_offset); const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = src_node_offset }); const cc_src = block.src(.{ .node_offset_fn_type_cc = src_node_offset }); @@ -9090,7 +9088,7 @@ fn funcCommon( .lbrace_column = @as(u16, @truncate(src_locs.columns)), .rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)), })); - try sema.ensureLayoutResolved(func_val.typeOf(zcu), src); + try sema.ensureLayoutResolved(func_val.typeOf(zcu), ret_ty_src, .return_type); return .fromValue(func_val); } @@ -9105,7 +9103,7 @@ fn funcCommon( }); if (has_body) { - try sema.ensureLayoutResolved(.fromInterned(func_ty), src); + try sema.ensureLayoutResolved(.fromInterned(func_ty), ret_ty_src, .return_type); return .fromIntern(try ip.getFuncDecl(gpa, io, pt.tid, .{ .owner_nav = sema.owner.unwrap().nav_val, .ty = func_ty, @@ -9761,7 +9759,7 @@ fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air return sema.failWithOwnedErrorMsg(block, msg); } try sema.checkIndexable(block, src, indexable_ty); - try sema.ensureLayoutResolved(indexable_ty.childType(zcu), src); + try sema.ensureLayoutResolved(indexable_ty.childType(zcu), src, .ptr_access); return sema.elemPtrOneLayerOnly(block, src, array_ptr, elem_index, src, false, false); } @@ -9975,14 +9973,15 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp const raw_operand_ty = sema.typeOf(eu_maybe_ptr); if (!non_err_case.operand_is_ref) break :err_union_ty raw_operand_ty; try sema.checkPtrOperand(block, operand_src, raw_operand_ty); - break :err_union_ty raw_operand_ty.childType(zcu); + const child_ty = raw_operand_ty.childType(zcu); + try sema.ensureLayoutResolved(child_ty, operand_src, .ptr_access); + break :err_union_ty child_ty; }; if (err_union_ty.zigTypeTag(zcu) != .error_union) { return sema.fail(block, operand_src, "expected error union type, found '{f}'", .{ err_union_ty.fmt(pt), }); } - try sema.ensureLayoutResolved(err_union_ty, operand_src); const non_err_cond = if (non_err_case.operand_is_ref) try sema.analyzePtrIsNonErr(block, operand_src, eu_maybe_ptr) @@ -11281,11 +11280,12 @@ fn validateSwitchBlock( const raw_operand_ty = sema.typeOf(raw_operand); if (operand_is_ref) { try sema.checkPtrType(block, operand_src, raw_operand_ty, false); - break :operand_ty raw_operand_ty.childType(zcu); + const child_ty = raw_operand_ty.childType(zcu); + try sema.ensureLayoutResolved(child_ty, operand_src, .ptr_access); + break :operand_ty child_ty; } break :operand_ty raw_operand_ty; }; - try sema.ensureLayoutResolved(operand_ty, operand_src); const item_ty: Type = item_ty: { switch (operand_ty.zigTypeTag(zcu)) { @@ -12844,7 +12844,7 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const name_src = block.builtinCallArgSrc(inst_data.src_node, 1); const ty = try sema.resolveType(block, ty_src, extra.lhs); const field_name = try sema.resolveConstStringIntern(block, name_src, extra.rhs, .{ .simple = .field_name }); - try sema.ensureLayoutResolved(ty, ty_src); + try sema.ensureLayoutResolved(ty, ty_src, .field_queried); const ip = &zcu.intern_pool; const has_field = hf: { @@ -15194,6 +15194,7 @@ fn analyzeArithmetic( }); } + try sema.ensureLayoutResolved(lhs_ty.childType(zcu), src, .ptr_offset); const elem_size = lhs_ty.childType(zcu).abiSize(zcu); if (elem_size == 0) { return sema.fail(block, src, "pointer subtraction requires element type '{f}' to have runtime bits", .{ @@ -15246,7 +15247,7 @@ fn analyzeArithmetic( else => return sema.failWithInvalidPtrArithmetic(block, src, "pointer-integer", "addition and subtraction"), }; - try sema.ensureLayoutResolved(lhs_ty.childType(zcu), src); + try sema.ensureLayoutResolved(lhs_ty.childType(zcu), src, .ptr_offset); return sema.analyzePtrArithmetic(block, src, lhs, rhs, air_tag, rhs_src); }, } @@ -15879,7 +15880,7 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const ty = try sema.resolveType(block, operand_src, inst_data.operand); - try sema.ensureLayoutResolved(ty, operand_src); + try sema.ensureLayoutResolved(ty, operand_src, .size_of); switch (ty.classify(zcu)) { .no_possible_value, => return sema.fail(block, operand_src, "no size available for uninstantiable type '{f}'", .{ty.fmt(pt)}), @@ -15934,7 +15935,7 @@ fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A .@"anyframe", => {}, } - try sema.ensureLayoutResolved(operand_ty, operand_src); + try sema.ensureLayoutResolved(operand_ty, operand_src, .size_of); return .fromValue(try pt.intValue(.comptime_int, operand_ty.bitSize(zcu))); } @@ -16185,7 +16186,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const type_info_ty = try sema.getBuiltinType(src, .Type); const type_info_tag_ty = type_info_ty.unionTagType(zcu).?; - try sema.ensureLayoutResolved(ty, src); + try sema.ensureLayoutResolved(ty, src, .type_info); if (ty.typeDeclInst(zcu)) |type_decl_inst| { try sema.declareDependency(.{ .namespace = type_decl_inst }); @@ -16345,7 +16346,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const alignment_val = try pt.intValue(.comptime_int, bytes: { if (info.flags.alignment.toByteUnits()) |b| break :bytes b; const elem_ty: Type = .fromInterned(info.child); - try sema.ensureLayoutResolved(elem_ty, src); + try sema.ensureLayoutResolved(elem_ty, src, .type_info); break :bytes elem_ty.abiAlignment(zcu).toByteUnits().?; }); @@ -18233,7 +18234,7 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air elem_ty.fmt(pt), bit_offset, bit_offset - host_size * 8, host_size, }); } - try sema.ensureLayoutResolved(elem_ty, elem_ty_src); + try sema.ensureLayoutResolved(elem_ty, elem_ty_src, .bit_ptr_child); const elem_bit_size = elem_ty.bitSize(zcu); if (elem_bit_size > host_size * 8 - bit_offset) { return sema.fail(block, bitoffset_src, "packed type '{f}' at bit offset {d} ends {d} bits after the end of a {d} byte host integer", .{ @@ -18302,7 +18303,7 @@ fn zirStructInitEmpty(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE const pt = sema.pt; const zcu = pt.zcu; - try sema.ensureLayoutResolved(obj_ty, ty_src); + try sema.ensureLayoutResolved(obj_ty, ty_src, .init); switch (obj_ty.zigTypeTag(zcu)) { .@"struct" => return sema.structInitEmpty(block, obj_ty, src, src), @@ -18367,7 +18368,7 @@ fn zirStructInitEmptyResult(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is }); } else ty_operand; - try sema.ensureLayoutResolved(init_ty, src); + try sema.ensureLayoutResolved(init_ty, src, .init); const obj_ty = init_ty.optEuBaseType(zcu); @@ -18486,7 +18487,7 @@ fn zirStructInit( // The type wasn't actually known, so treat this as an anon struct init. return sema.structInitAnon(block, src, inst, .typed_init, extra.data, extra.end, is_ref); }; - try sema.ensureLayoutResolved(result_ty, src); + try sema.ensureLayoutResolved(result_ty, src, .init); const resolved_ty = result_ty.optEuBaseType(zcu); if (resolved_ty.zigTypeTag(zcu) == .@"struct") { @@ -18951,7 +18952,7 @@ fn structInitAnon( }; try sema.addTypeReferenceEntry(src, struct_ty); // No need for `ensureNamespaceUpToDate` because this type's namespace is always empty. - try sema.ensureLayoutResolved(struct_ty, src); + try sema.ensureLayoutResolved(struct_ty, src, .init); _ = opt_runtime_index orelse { const struct_val = try pt.aggregateValue(struct_ty, values); @@ -19270,7 +19271,7 @@ fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro const field_src = block.builtinCallArgSrc(inst_data.src_node, 1); const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, .{ .simple = .field_name }); - try sema.ensureLayoutResolved(aggregate_ty, ty_src); + try sema.ensureLayoutResolved(aggregate_ty, ty_src, .field_queried); return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); } @@ -19290,7 +19291,7 @@ fn zirStructInitFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp const aggregate_ty = wrapped_aggregate_ty.optEuBaseType(zcu); const zir_field_name = sema.code.nullTerminatedString(extra.name_start); const field_name = try ip.getOrPutString(gpa, io, pt.tid, zir_field_name, .no_embedded_nulls); - try sema.ensureLayoutResolved(aggregate_ty, ty_src); + try sema.ensureLayoutResolved(aggregate_ty, ty_src, .init); return sema.fieldType(block, aggregate_ty, field_name, field_name_src, ty_src); } @@ -19390,7 +19391,7 @@ fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); const ty = try sema.resolveType(block, operand_src, inst_data.operand); - try sema.ensureLayoutResolved(ty, operand_src); + try sema.ensureLayoutResolved(ty, operand_src, .align_of); if (ty.isNoReturn(zcu)) { return sema.fail(block, operand_src, "no align available for type '{f}'", .{ty.fmt(sema.pt)}); } @@ -19874,7 +19875,6 @@ fn zirReifyFn( const param_attrs_arr = try sema.derefSliceAsArray(block, param_attrs_src, param_attrs_slice, .{ .simple = .fn_param_attrs }); const ret_ty = try sema.resolveType(block, ret_ty_src, extra.ret_ty); - try sema.ensureLayoutResolved(ret_ty, ret_ty_src); const fn_attrs_uncoerced = sema.resolveInst(extra.fn_attrs); const fn_attrs_coerced = try sema.coerce(block, fn_attrs_ty, fn_attrs_uncoerced, fn_attrs_src); @@ -19899,10 +19899,6 @@ fn zirReifyFn( param_types_src, fn_attrs.@"callconv", ); - try sema.ensureLayoutResolved(param_ty, param_types_src); - if (param_ty.comptimeOnly(zcu)) { - return sema.fail(block, param_attrs_src, "cannot reify function type with comptime-only parameter type '{f}'", .{param_ty.fmt(pt)}); - } if (param_attrs.@"noalias") { if (param_idx > 31) { return sema.fail(block, param_attrs_src, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{}); @@ -20811,8 +20807,7 @@ fn zirPtrFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const elem_ty = ptr_ty.nullablePtrElem(zcu); - // We'll need to validate the pointer alignment. - try sema.ensureLayoutResolved(elem_ty, src); + try sema.ensureLayoutResolved(elem_ty, src, .align_check); const ptr_align = ptr_ty.ptrAlignment(zcu); if (ptr_ty.isSlice(zcu)) { @@ -21155,8 +21150,8 @@ fn ptrCastFull( const src_info = operand_ty.ptrInfo(zcu); const dest_info = dest_ty.ptrInfo(zcu); - try sema.ensureLayoutResolved(.fromInterned(src_info.child), operand_src); - try sema.ensureLayoutResolved(.fromInterned(dest_info.child), src); + try sema.ensureLayoutResolved(.fromInterned(src_info.child), operand_src, .align_check); + try sema.ensureLayoutResolved(.fromInterned(dest_info.child), src, .align_check); const DestSliceLen = union(enum) { undef, @@ -21927,7 +21922,7 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6 const ty = try sema.resolveType(block, ty_src, extra.lhs); const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.rhs, .{ .simple = .field_name }); - try sema.ensureLayoutResolved(ty, ty_src); + try sema.ensureLayoutResolved(ty, ty_src, .field_queried); const pt = sema.pt; const zcu = pt.zcu; @@ -23010,7 +23005,7 @@ fn zirAtomicLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, true); const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering, .{ .simple = .atomic_order }); - try sema.ensureLayoutResolved(elem_ty, elem_ty_src); + try sema.ensureLayoutResolved(elem_ty, elem_ty_src, .ptr_access); switch (order) { .release, .acq_rel => { @@ -23330,7 +23325,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.Ins return sema.fail(block, inst_src, "expected single pointer type, found '{f}'", .{parent_ptr_ty.fmt(pt)}); } const parent_ty: Type = .fromInterned(parent_ptr_info.child); - try sema.ensureLayoutResolved(parent_ty, inst_src); + try sema.ensureLayoutResolved(parent_ty, inst_src, .field_used); switch (parent_ty.zigTypeTag(zcu)) { .@"struct", .@"union" => {}, else => return sema.fail(block, inst_src, "expected pointer to struct or union type, found '{f}'", .{parent_ptr_ty.fmt(pt)}), @@ -23938,8 +23933,8 @@ fn zirMemcpy( const dest_elem_ty = dest_ty.indexableElem(zcu); const src_elem_ty = src_ty.indexableElem(zcu); - try sema.ensureLayoutResolved(dest_elem_ty, dest_src); - try sema.ensureLayoutResolved(src_elem_ty, src_src); + try sema.ensureLayoutResolved(dest_elem_ty, dest_src, .ptr_access); + try sema.ensureLayoutResolved(src_elem_ty, src_src, .ptr_access); const imc = try sema.coerceInMemoryAllowed( block, @@ -25470,7 +25465,7 @@ fn fieldPtrLoad( const object_ptr_ty = sema.typeOf(object_ptr); assert(object_ptr_ty.zigTypeTag(zcu) == .pointer); const pointee_ty = object_ptr_ty.childType(zcu); - try sema.ensureLayoutResolved(pointee_ty, src); + try sema.ensureLayoutResolved(pointee_ty, src, .ptr_access); if (try pointee_ty.onePossibleValue(pt)) |opv| { const object: Air.Inst.Ref = .fromValue(opv); return fieldVal(sema, block, src, object, field_name, field_name_src); @@ -25606,7 +25601,7 @@ fn fieldVal( if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| { return inst; } - try sema.ensureLayoutResolved(child_type, src); + try sema.ensureLayoutResolved(child_type, src, .field_used); if (child_type.unionTagType(zcu)) |enum_ty| { if (enum_ty.enumFieldIndex(field_name, zcu)) |field_index_usize| { const field_index: u32 = @intCast(field_index_usize); @@ -25619,7 +25614,7 @@ fn fieldVal( if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| { return inst; } - try sema.ensureLayoutResolved(child_type, src); + try sema.ensureLayoutResolved(child_type, src, .field_used); const field_index_usize = child_type.enumFieldIndex(field_name, zcu) orelse return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); const field_index: u32 = @intCast(field_index_usize); @@ -25645,7 +25640,7 @@ fn fieldVal( }, .@"struct" => if (is_pointer_to) { // Avoid loading the entire struct by fetching a pointer and loading that - try sema.ensureLayoutResolved(inner_ty, src); + try sema.ensureLayoutResolved(inner_ty, src, .ptr_access); const field_ptr = try sema.structFieldPtr(block, src, object, field_name, field_name_src, inner_ty); return sema.analyzeLoad(block, src, field_ptr, object_src); } else { @@ -25653,7 +25648,7 @@ fn fieldVal( }, .@"union" => if (is_pointer_to) { // Avoid loading the entire union by fetching a pointer and loading that - try sema.ensureLayoutResolved(inner_ty, src); + try sema.ensureLayoutResolved(inner_ty, src, .ptr_access); const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false); return sema.analyzeLoad(block, src, field_ptr, object_src); } else { @@ -25837,7 +25832,7 @@ fn fieldPtr( if (try sema.namespaceLookupRef(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| { return inst; } - try sema.ensureLayoutResolved(child_type, src); + try sema.ensureLayoutResolved(child_type, src, .field_used); if (child_type.unionTagType(zcu)) |enum_ty| { if (enum_ty.enumFieldIndex(field_name, zcu)) |field_index| { const field_index_u32: u32 = @intCast(field_index); @@ -25851,7 +25846,7 @@ fn fieldPtr( if (try sema.namespaceLookupRef(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| { return inst; } - try sema.ensureLayoutResolved(child_type, src); + try sema.ensureLayoutResolved(child_type, src, .field_used); const field_index = child_type.enumFieldIndex(field_name, zcu) orelse { return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); }; @@ -25873,7 +25868,7 @@ fn fieldPtr( try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) else object_ptr; - try sema.ensureLayoutResolved(inner_ty, src); + try sema.ensureLayoutResolved(inner_ty, src, .ptr_access); const field_ptr = try sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr); return field_ptr; @@ -25883,7 +25878,7 @@ fn fieldPtr( try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) else object_ptr; - try sema.ensureLayoutResolved(inner_ty, src); + try sema.ensureLayoutResolved(inner_ty, src, .ptr_access); const field_ptr = try sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing); try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr); return field_ptr; @@ -25927,7 +25922,7 @@ fn fieldCallBind( // Optionally dereference a second pointer to get the concrete type. const is_double_ptr = inner_ty.zigTypeTag(zcu) == .pointer and inner_ty.ptrSize(zcu) == .one; const concrete_ty = if (is_double_ptr) inner_ty.childType(zcu) else inner_ty; - try sema.ensureLayoutResolved(concrete_ty, src); + try sema.ensureLayoutResolved(concrete_ty, src, .ptr_access); const ptr_ty = if (is_double_ptr) inner_ty else raw_ptr_ty; const object_ptr = if (is_double_ptr) try sema.analyzeLoad(block, src, raw_ptr, src) @@ -26514,7 +26509,7 @@ fn elemPtr( else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{f}'", .{indexable_ptr_ty.fmt(pt)}), }; try sema.checkIndexable(block, src, indexable_ty); - try sema.ensureLayoutResolved(indexable_ty, src); + try sema.ensureLayoutResolved(indexable_ty, src, .ptr_access); const elem_ptr = switch (indexable_ty.zigTypeTag(zcu)) { .vector => try sema.elemPtrVector(block, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init), @@ -26522,7 +26517,7 @@ fn elemPtr( .@"struct" => try sema.tupleElemPtr(block, src, indexable_ptr, elem_index, elem_index_src), else => { const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src); - try sema.ensureLayoutResolved(sema.typeOf(indexable).childType(zcu), src); + try sema.ensureLayoutResolved(sema.typeOf(indexable).childType(zcu), src, .ptr_access); return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init, oob_safety); }, }; @@ -26614,7 +26609,7 @@ fn elemVal( switch (indexable_ty.zigTypeTag(zcu)) { .pointer => { const child_ty = indexable_ty.childType(zcu); - try sema.ensureLayoutResolved(child_ty, src); + try sema.ensureLayoutResolved(child_ty, src, .ptr_access); switch (indexable_ty.ptrSize(zcu)) { .slice => return sema.elemValSlice(block, src, indexable_src, indexable, elem_index_src, elem_index, oob_safety), .many, .c => { @@ -27192,7 +27187,7 @@ fn coerceExtra( const target = zcu.getTarget(); inst_ty.assertHasLayout(zcu); - try sema.ensureLayoutResolved(dest_ty, inst_src); + try sema.ensureLayoutResolved(dest_ty, inst_src, .coerce); // If the types are the same, we can return the operand. if (dest_ty.eql(inst_ty, zcu)) @@ -28552,8 +28547,8 @@ fn coerceInMemoryAllowedFns( } }; } - try sema.ensureLayoutResolved(src_ty, src_src); - try sema.ensureLayoutResolved(dest_ty, dest_src); + try sema.ensureLayoutResolved(src_ty, src_src, .coerce); + try sema.ensureLayoutResolved(dest_ty, dest_src, .coerce); const src_is_runtime = src_ty.fnHasRuntimeBits(zcu); const dest_is_runtime = dest_ty.fnHasRuntimeBits(zcu); if (src_is_runtime != dest_is_runtime) return .{ .fn_generic = !dest_is_runtime }; @@ -28607,7 +28602,6 @@ fn coerceInMemoryAllowedFns( const src_is_comptime = src_info.paramIsComptime(@intCast(param_i)); const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i)); if (src_is_comptime == dest_is_comptime) break :comptime_param; - try sema.ensureLayoutResolved(dest_param_ty, dest_src); if (!dest_is_mut and src_is_comptime and !dest_is_comptime and dest_param_ty.comptimeOnly(zcu)) { // A parameter which is marked `comptime` can drop that annotation if the type is comptime-only. // The function remains generic, and the parameter is going to be comptime-resolved either way, @@ -28835,11 +28829,11 @@ fn coerceInMemoryAllowedPtrs( dest_info.child != src_info.child) { const src_align = if (src_info.flags.alignment == .none) a: { - try sema.ensureLayoutResolved(src_child, src_src); + try sema.ensureLayoutResolved(src_child, src_src, .align_check); break :a src_child.abiAlignment(zcu); } else src_info.flags.alignment; const dest_align = if (dest_info.flags.alignment == .none) a: { - try sema.ensureLayoutResolved(dest_child, dest_src); + try sema.ensureLayoutResolved(dest_child, dest_src, .align_check); break :a dest_child.abiAlignment(zcu); } else dest_info.flags.alignment; if (dest_align.compare(if (dest_is_mut) .neq else .gt, src_align)) { @@ -29189,7 +29183,7 @@ fn bitCast( const old_ty = sema.typeOf(inst); old_ty.assertHasLayout(zcu); - try sema.ensureLayoutResolved(dest_ty, inst_src); + try sema.ensureLayoutResolved(dest_ty, inst_src, .init); const dest_bits = dest_ty.bitSize(zcu); const old_bits = old_ty.bitSize(zcu); @@ -29837,10 +29831,11 @@ fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.M try sema.addReferenceEntry(null, src, unit); try sema.declareDependency(.{ .memoized_state = stage }); + const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined }; if (pt.zcu.analysis_in_progress.contains(unit)) { - return sema.failWithOwnedErrorMsg(null, try sema.errMsg(src, "dependency loop detected", .{})); + return sema.failWithDependencyLoop(unit, &reason); } - try pt.ensureMemoizedStateUpToDate(stage); + try pt.ensureMemoizedStateUpToDate(stage, &reason); } pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void { @@ -29854,11 +29849,6 @@ pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: return; } - try sema.declareDependency(switch (kind) { - .type => .{ .nav_ty = nav_index }, - .fully => .{ .nav_val = nav_index }, - }); - // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate` // to make sure the value is up-to-date on incremental updates. @@ -29867,20 +29857,23 @@ pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: .fully => .{ .nav_val = nav_index }, }); try sema.addReferenceEntry(block, src, anal_unit); + try sema.declareDependency(switch (kind) { + .type => .{ .nav_ty = nav_index }, + .fully => .{ .nav_val = nav_index }, + }); + + const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined }; if (zcu.analysis_in_progress.contains(anal_unit)) { - return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{ - .base_node_inst = nav.analysis.?.zir_index, - .offset = LazySrcLoc.Offset.nodeOffset(.zero), - }, "dependency loop detected", .{})); + return sema.failWithDependencyLoop(anal_unit, &reason); } switch (kind) { .type => { try zcu.ensureNavValAnalysisQueued(nav_index); - return pt.ensureNavTypeUpToDate(nav_index); + return pt.ensureNavTypeUpToDate(nav_index, &reason); }, - .fully => return pt.ensureNavValUpToDate(nav_index), + .fully => return pt.ensureNavValUpToDate(nav_index, &reason), } } @@ -30065,7 +30058,7 @@ fn analyzeLoad( return sema.fail(block, ptr_src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)}); } - try sema.ensureLayoutResolved(elem_ty, src); + try sema.ensureLayoutResolved(elem_ty, src, .ptr_access); if (try elem_ty.onePossibleValue(pt)) |opv| return .fromValue(opv); if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { @@ -30249,7 +30242,7 @@ fn resolveIsNonErrFromType( // This is *our* error set; that is, we're currently analyzing the function // which owns it. Trying to resolve it now would cause a dependency loop. // Instead, accept that we don't know. - if (true) return null; + return null; }, else => |set_ty| switch (ip.indexToKey(set_ty)) { .error_set_type => |error_set_type| switch (error_set_type.names.len) { @@ -30450,7 +30443,7 @@ fn analyzeSlice( else => return sema.fail(block, src, "slice of non-array type '{f}'", .{ptr_ptr_child_ty.fmt(pt)}), } - try sema.ensureLayoutResolved(elem_ty, src); + try sema.ensureLayoutResolved(elem_ty, src, .ptr_access); const ptr = if (slice_ty.isSlice(zcu)) try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty) @@ -32831,11 +32824,13 @@ fn ensureFuncIesResolved( try sema.declareDependency(.{ .func_ies = func_index }); try sema.addReferenceEntry(block, src, .wrap(.{ .func = func_index })); + const reason: Zcu.DependencyReason = .{ .src = src, .type_layout_reason = undefined }; + if (zcu.analysis_in_progress.contains(.wrap(.{ .func = func_index }))) { - return sema.fail(block, src, "unable to resolve inferred error set", .{}); + return sema.failWithDependencyLoop(.wrap(.{ .func = func_index }), &reason); } - try pt.ensureFuncBodyUpToDate(func_index); + try pt.ensureFuncBodyUpToDate(func_index, &reason); } pub fn resolveInferredErrorSetPtr( @@ -33749,6 +33744,8 @@ pub fn flushExports(sema: *Sema) !void { // // So, pick up and delete any existing exports. This strategy performs // redundant work, but that's okay, because this case is exceedingly rare. + // + // MLUGG TODO: is this still possible? if not, delete this logic and combine deleteUnitExports into resetUnit if (zcu.single_exports.get(sema.owner)) |export_idx| { try sema.exports.append(gpa, export_idx.ptr(zcu).*); } else if (zcu.multi_exports.get(sema.owner)) |info| { @@ -33940,7 +33937,7 @@ pub fn analyzeMemoizedState(sema: *Sema, stage: InternPool.MemoizedStateStage) C const val: Value = switch (builtin_decl.kind()) { .type => val: { const ty = try sema.analyzeAsType(&block, decl_src, .std_builtin_decl, uncoerced_val); - try sema.ensureLayoutResolved(ty, decl_src); + try sema.ensureLayoutResolved(ty, decl_src, .builtin_type); break :val ty.toValue(); }, .func => val: { @@ -34370,3 +34367,42 @@ fn zirOpaqueDecl( return .fromType(ty); } + +/// Registers an error indicating a dependency loop: we have introduced a dependency on `want` (with +/// reason `want_reason`) but have learnt that `want` is already in `zcu.analysis_in_progress`. +pub fn failWithDependencyLoop( + sema: *Sema, + want: AnalUnit, + want_reason: *const Zcu.DependencyReason, +) SemaError { + const pt = sema.pt; + const zcu = pt.zcu; + const gpa = zcu.comp.gpa; + + const in_progress_len = zcu.analysis_in_progress.count(); + var index = zcu.analysis_in_progress.getIndex(want).? + 1; + + try zcu.dependency_loops.ensureUnusedCapacity(gpa, 1); + try zcu.dependency_loop_nodes.ensureUnusedCapacity(gpa, in_progress_len - index + 1); + + zcu.dependency_loops.putAssumeCapacityNoClobber(want, {}); + + while (index <= in_progress_len) : (index += 1) { + const parent_unit = zcu.analysis_in_progress.keys()[index - 1]; + const unit, const reason = if (index == in_progress_len) .{ + want, + want_reason, + } else .{ + zcu.analysis_in_progress.keys()[index], + zcu.analysis_in_progress.values()[index], + }; + + zcu.dependency_loop_nodes.putAssumeCapacityNoClobber(parent_unit, .{ + .unit = unit, + .reason = reason.?.*, + }); + } + + // A dependency loop error will be reported. Mark us all as transitive failures. + return error.AnalysisFail; +} diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index b755c5daaa..cf809603c0 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -300,7 +300,7 @@ fn checkTypeInner( } else { const gop = try visited.getOrPut(sema.arena, ty.toIntern()); if (gop.found_existing) return; - try sema.ensureLayoutResolved(ty, self.import_loc); + try sema.ensureLayoutResolved(ty, self.import_loc, .init); const struct_info = zcu.typeToStruct(ty).?; for (struct_info.field_types.get(ip)) |field_type| { try self.checkTypeInner(.fromInterned(field_type), null, visited); @@ -309,7 +309,7 @@ fn checkTypeInner( .@"union" => { const gop = try visited.getOrPut(sema.arena, ty.toIntern()); if (gop.found_existing) return; - try sema.ensureLayoutResolved(ty, self.import_loc); + try sema.ensureLayoutResolved(ty, self.import_loc, .init); const union_info = zcu.typeToUnion(ty).?; for (union_info.field_types.get(ip)) |field_type| { if (field_type != .void_type) { @@ -646,7 +646,7 @@ fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I const gpa = comp.gpa; const io = comp.io; const ip = &pt.zcu.intern_pool; - try self.sema.ensureLayoutResolved(res_ty, self.import_loc); + try self.sema.ensureLayoutResolved(res_ty, self.import_loc, .init); switch (node.get(self.file.zoir.?)) { .enum_literal => |field_name| { const field_name_interned = try ip.getOrPutString( @@ -769,7 +769,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const io = comp.io; const ip = &pt.zcu.intern_pool; - try self.sema.ensureLayoutResolved(res_ty, self.import_loc); + try self.sema.ensureLayoutResolved(res_ty, self.import_loc, .init); const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { @@ -919,7 +919,7 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const gpa = comp.gpa; const io = comp.io; const ip = &pt.zcu.intern_pool; - try self.sema.ensureLayoutResolved(res_ty, self.import_loc); + try self.sema.ensureLayoutResolved(res_ty, self.import_loc, .init); const union_info = pt.zcu.typeToUnion(res_ty).?; const enum_tag_info = ip.loadEnumType(union_info.enum_tag_type); diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index a7b862289b..07349457a4 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -14,12 +14,64 @@ const InternPool = @import("../InternPool.zig"); const Alignment = InternPool.Alignment; const arith = @import("arith.zig"); +pub const LayoutResolveReason = enum { + variable, + constant, + parameter, + return_type, + field, + backing_enum, + init, + coerce, + ptr_access, + ptr_offset, + field_used, + field_queried, + size_of, + align_of, + type_info, + align_check, + bit_ptr_child, + builtin_type, + + /// Written after string: "while resolving type 'T' " + /// e.g. "while resolving type 'MyStruct' for variable declared here" + pub fn msg(r: LayoutResolveReason) []const u8 { + return switch (r) { + // zig fmt: off + .variable => "for variable declared here", + .constant => "for constant declared here", + .parameter => "for function parameter declared here", + .return_type => "for function return type declared here", + .field => "for field declared here", + .backing_enum => "for backing enum type declared here", + .init => "for initialization performed here", + .coerce => "for coercion performed here", + .ptr_access => "for pointer access here", + .ptr_offset => "for pointer offset here", + .field_used => "for field usage here", + .field_queried => "for field query here", + .size_of => "for size query here", + .align_of => "for alignment query here", + .type_info => "for type information query here", + .align_check => "for alignment check here", + .bit_ptr_child => "for bit size check here", + .builtin_type => "from 'std.builtin'", + // zig fmt: on + }; + } +}; + /// Ensures that `ty` has known layout, including alignment, size, and (where relevant) field offsets. /// `ty` may be any type; its layout is resolved *recursively* if necessary. /// Adds incremental dependencies tracking any required type resolution. -/// MLUGG TODO: to make the langspec non-stupid, we need to call this from WAY fewer places (the conditions need to be less specific). -/// MLUGG TODO: to be clear, i should audit EVERY use of this before PRing -pub fn ensureLayoutResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!void { +pub fn ensureLayoutResolved(sema: *Sema, ty: Type, src: LazySrcLoc, reason: LayoutResolveReason) SemaError!void { + return ensureLayoutResolvedInner(sema, ty, ty, &.{ + .src = src, + .type_layout_reason = reason, + }); +} +fn ensureLayoutResolvedInner(sema: *Sema, ty: Type, orig_ty: Type, reason: *const Zcu.DependencyReason) SemaError!void { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; @@ -35,30 +87,25 @@ pub fn ensureLayoutResolved(sema: *Sema, ty: Type, src: LazySrcLoc) SemaError!vo .func_type => |func_type| { for (func_type.param_types.get(ip)) |param_ty| { - try ensureLayoutResolved(sema, .fromInterned(param_ty), src); + try ensureLayoutResolvedInner(sema, .fromInterned(param_ty), orig_ty, reason); } - try ensureLayoutResolved(sema, .fromInterned(func_type.return_type), src); + try ensureLayoutResolvedInner(sema, .fromInterned(func_type.return_type), orig_ty, reason); }, - .array_type => |arr| return ensureLayoutResolved(sema, .fromInterned(arr.child), src), - .vector_type => |vec| return ensureLayoutResolved(sema, .fromInterned(vec.child), src), - .opt_type => |child| return ensureLayoutResolved(sema, .fromInterned(child), src), - .error_union_type => |eu| return ensureLayoutResolved(sema, .fromInterned(eu.payload_type), src), + .array_type => |arr| return ensureLayoutResolvedInner(sema, .fromInterned(arr.child), orig_ty, reason), + .vector_type => |vec| return ensureLayoutResolvedInner(sema, .fromInterned(vec.child), orig_ty, reason), + .opt_type => |child| return ensureLayoutResolvedInner(sema, .fromInterned(child), orig_ty, reason), + .error_union_type => |eu| return ensureLayoutResolvedInner(sema, .fromInterned(eu.payload_type), orig_ty, reason), .tuple_type => |tuple| for (tuple.types.get(ip)) |field_ty| { - try ensureLayoutResolved(sema, .fromInterned(field_ty), src); + try ensureLayoutResolvedInner(sema, .fromInterned(field_ty), orig_ty, reason); }, .struct_type, .union_type, .enum_type => { try sema.declareDependency(.{ .type_layout = ty.toIntern() }); - try sema.addReferenceEntry(null, src, .wrap(.{ .type_layout = ty.toIntern() })); + try sema.addReferenceEntry(null, reason.src, .wrap(.{ .type_layout = ty.toIntern() })); if (zcu.analysis_in_progress.contains(.wrap(.{ .type_layout = ty.toIntern() }))) { - // TODO: better error message - return sema.failWithOwnedErrorMsg(null, try sema.errMsg( - ty.srcLoc(zcu), - "{s} '{f}' depends on itself", - .{ @tagName(ty.zigTypeTag(zcu)), ty.fmt(pt) }, - )); + return sema.failWithDependencyLoop(.wrap(.{ .type_layout = ty.toIntern() }), reason); } - try pt.ensureTypeLayoutUpToDate(ty); + try pt.ensureTypeLayoutUpToDate(ty, reason); }, // values, not types @@ -228,7 +275,7 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { const field_ty: Type = .fromInterned(field_ty_ip); assert(!field_ty.isGenericPoison()); const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); - try sema.ensureLayoutResolved(field_ty, field_ty_src); + try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); if (field_ty.zigTypeTag(zcu) == .@"opaque") { return sema.failWithOwnedErrorMsg(&block, msg: { @@ -387,7 +434,7 @@ fn resolvePackedStructLayout( const field_ty: Type = .fromInterned(field_ty_ip); assert(!field_ty.isGenericPoison()); const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); - try sema.ensureLayoutResolved(field_ty, field_ty_src); + try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); if (field_ty.zigTypeTag(zcu) == .@"opaque") { return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in struct", .{field_ty.fmt(pt)}); @@ -557,7 +604,7 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { }, }; - try sema.ensureLayoutResolved(enum_tag_ty, block.src(.container_arg)); + try sema.ensureLayoutResolved(enum_tag_ty, block.src(.container_arg), .backing_enum); const enum_obj = ip.loadEnumType(enum_tag_ty.toIntern()); if (union_obj.is_reified) { @@ -652,7 +699,7 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { const field_ty: Type = .fromInterned(field_ty_ip); assert(!field_ty.isGenericPoison()); const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); - try sema.ensureLayoutResolved(field_ty, field_ty_src); + try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); if (field_ty.zigTypeTag(zcu) == .@"opaque") { return sema.failWithOwnedErrorMsg(&block, msg: { const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in union", .{field_ty.fmt(pt)}); @@ -832,7 +879,7 @@ fn resolvePackedUnionLayout( const field_ty: Type = .fromInterned(field_ty_ip); assert(!field_ty.isGenericPoison()); const field_ty_src = block.src(.{ .container_field_type = @intCast(field_index) }); - try sema.ensureLayoutResolved(field_ty, field_ty_src); + try sema.ensureLayoutResolved(field_ty, field_ty_src, .field); if (field_ty.zigTypeTag(zcu) == .@"opaque") { return sema.failWithOwnedErrorMsg(block, msg: { const msg = try sema.errMsg(field_ty_src, "cannot directly embed opaque type '{f}' in union", .{field_ty.fmt(pt)}); diff --git a/src/Zcu.zig b/src/Zcu.zig index 19702aee08..49abdbed56 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -119,7 +119,7 @@ module_roots: std.AutoArrayHashMapUnmanaged(*Package.Module, File.Index.Optional /// /// Always accessed through `ImportTableAdapter`, where keys are fully resolved /// file paths in order to ensure files are properly deduplicated. This table owns -/// the keys and values. +/// the keysand values. /// /// Protected by Compilation's mutex. /// @@ -177,7 +177,9 @@ embed_table: std.ArrayHashMapUnmanaged( /// is not yet implemented. intern_pool: InternPool = .empty, -analysis_in_progress: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty, +/// Value explains why this `AnalUnit` is being analyzed. It is `null` for the topmost analysis +/// (index 0), and non-`null` for all others. +analysis_in_progress: std.AutoArrayHashMapUnmanaged(AnalUnit, ?*const DependencyReason) = .empty, /// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator. failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, *ErrorMsg) = .empty, /// This `AnalUnit` failed semantic analysis because it required analysis of another `AnalUnit` which itself failed. @@ -189,6 +191,19 @@ transitive_failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .emp /// codegen and linking run on a separate thread. failed_codegen: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, *ErrorMsg) = .empty, failed_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, *ErrorMsg) = .empty, + +/// Key is an `AnalUnit` which is in `dependency_loop_nodes`. For each dependency loop, exactly one +/// unit in the loop is in this map, though the choice is arbitrary and not necessarily reproducible +/// between compilations. So, instead of (for instance) defining where the dependency loop "starts", +/// this map simply exists to allow easily iterating all dependency loops exactly once. +dependency_loops: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty, +/// Key is an `AnalUnit`, value is the `AnalUnit` which the key references and why it does so. +/// All units in here form loops. To iterate loops, see `dependency_loops`. +dependency_loop_nodes: std.AutoArrayHashMapUnmanaged(AnalUnit, struct { + unit: AnalUnit, + reason: DependencyReason, +}) = .empty, + /// Keep track of `@compileLog`s per `AnalUnit`. /// We track the source location of the first `@compileLog` call, and all logged lines as a linked list. /// The list is singly linked, but we do track its tail for fast appends (optimizing many logs in one unit). @@ -321,6 +336,12 @@ codegen_task_pool: CodegenTaskPool, generation: u32 = 0, +pub const DependencyReason = struct { + src: LazySrcLoc, + /// Only populated if this is for a `.type_layout` unit. + type_layout_reason: Sema.type_resolution.LayoutResolveReason, +}; + pub const IncrementalDebugState = struct { /// All container types in the ZCU, even dead ones. /// Value is the generation the type was created on. @@ -2778,6 +2799,8 @@ pub fn deinit(zcu: *Zcu) void { zcu.analysis_in_progress.deinit(gpa); zcu.failed_analysis.deinit(gpa); zcu.transitive_failed_analysis.deinit(gpa); + zcu.dependency_loops.deinit(gpa); + zcu.dependency_loop_nodes.deinit(gpa); zcu.failed_codegen.deinit(gpa); zcu.failed_types.deinit(gpa); @@ -3536,15 +3559,47 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void { } } -/// Delete all references in `reference_table` which are caused by this `AnalUnit`. +/// Prepares `unit` for re-analysis by clearing all of the following state: +/// * Compile errors associated with `unit` +/// * Compile logs associated with `unit` +/// * Dependencies from `unit` on other things +/// * References from `unit` to other units +/// Delete all references in `reference_table` which are caused by `unit`, and all dependencies it +/// has. Called in preparation for re-analysis, which will recreate references and dependencies. /// Re-analysis of the `AnalUnit` will cause appropriate references to be recreated. -pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { - const gpa = zcu.gpa; +pub fn resetUnit(zcu: *Zcu, unit: AnalUnit) void { + const gpa = zcu.comp.gpa; + // Compile errors + if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| { + kv.value.destroy(gpa); + } else if (zcu.dependency_loop_nodes.swapRemove(unit)) { + _ = zcu.dependency_loops.swapRemove(unit); + _ = zcu.transitive_failed_analysis.swapRemove(unit); + } else { + _ = zcu.transitive_failed_analysis.swapRemove(unit); + } + + // Compile logs + if (zcu.compile_logs.fetchSwapRemove(unit)) |kv| { + var opt_line_idx = kv.value.first_line.toOptional(); + while (opt_line_idx.unwrap()) |line_idx| { + zcu.free_compile_log_lines.append(gpa, line_idx) catch { + // This space will be reused eventually, so we need not propagate this error. + // Just leak it for now, and let GC reclaim it later on. + break; + }; + opt_line_idx = line_idx.get(zcu).next; + } + } + + // Dependencies + zcu.intern_pool.removeDependenciesForDepender(gpa, unit); + + // References zcu.clearCachedResolvedReferences(); - unit_refs: { - const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse break :unit_refs; + const kv = zcu.reference_table.fetchSwapRemove(unit) orelse break :unit_refs; var idx = kv.value; while (idx != std.math.maxInt(u32)) { @@ -3572,9 +3627,8 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { } } } - type_refs: { - const kv = zcu.type_reference_table.fetchSwapRemove(anal_unit) orelse break :type_refs; + const kv = zcu.type_reference_table.fetchSwapRemove(unit) orelse break :type_refs; var idx = kv.value; while (idx != std.math.maxInt(u32)) { @@ -3588,22 +3642,6 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { } } -/// Delete all compile logs performed by this `AnalUnit`. -/// Re-analysis of the `AnalUnit` will cause logs to be rediscovered. -pub fn deleteUnitCompileLogs(zcu: *Zcu, anal_unit: AnalUnit) void { - const kv = zcu.compile_logs.fetchSwapRemove(anal_unit) orelse return; - const gpa = zcu.gpa; - var opt_line_idx = kv.value.first_line.toOptional(); - while (opt_line_idx.unwrap()) |line_idx| { - zcu.free_compile_log_lines.append(gpa, line_idx) catch { - // This space will be reused eventually, so we need not propagate this error. - // Just leak it for now, and let GC reclaim it later on. - return; - }; - opt_line_idx = line_idx.get(zcu).next; - } -} - pub fn addInlineReferenceFrame(zcu: *Zcu, frame: InlineReferenceFrame) Allocator.Error!Zcu.InlineReferenceFrame.Index { const frame_idx: InlineReferenceFrame.Index = zcu.free_inline_reference_frames.pop() orelse idx: { _ = try zcu.inline_reference_frames.addOne(zcu.gpa); @@ -4252,11 +4290,7 @@ pub fn fmtDependee(zcu: *Zcu, d: InternPool.Dependee) std.fmt.Alt(FormatDependee return .{ .data = .{ .dependee = d, .zcu = zcu } }; } -const FormatAnalUnit = struct { - unit: AnalUnit, - zcu: *Zcu, -}; - +const FormatAnalUnit = struct { unit: AnalUnit, zcu: *const Zcu }; fn formatAnalUnit(data: FormatAnalUnit, writer: *Io.Writer) Io.Writer.Error!void { const zcu = data.zcu; const ip = &zcu.intern_pool; @@ -4280,8 +4314,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *Io.Writer) Io.Writer.Error!void } } -const FormatDependee = struct { dependee: InternPool.Dependee, zcu: *Zcu }; - +const FormatDependee = struct { dependee: InternPool.Dependee, zcu: *const Zcu }; fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void { const zcu = data.zcu; const ip = &zcu.intern_pool; @@ -4666,6 +4699,273 @@ fn explainWhyFileIsInModule( } } +pub fn addDependencyLoopErrors(zcu: *Zcu, eb: *std.zig.ErrorBundle.Wip) Allocator.Error!void { + const gpa = zcu.comp.gpa; + + const all_references = try zcu.resolveReferences(); + + var units: std.ArrayList(AnalUnit) = .empty; + defer units.deinit(gpa); + + // TODO: sort the dependency loops somehow to make the error bundle reproducible + for (zcu.dependency_loops.keys()) |arbitrary_unit| { + units.clearRetainingCapacity(); + + var cur = arbitrary_unit; + while (true) { + try units.append(gpa, cur); + cur = zcu.dependency_loop_nodes.get(cur).?.unit; + if (cur == arbitrary_unit) break; + } + + // `units` now contains all units in the loop. We need to pick a starting point somewhere + // along that loop to begin. We will pick whichever node has the shortest reference trace, + // because the other units may well just be referenced *by* that one! This is also likely + // to match the user's intuition for where the loop "starts". + var start_index: usize = 0; + var start_depth: u32 = depth: { + var depth: u32 = 0; + var opt_ref = all_references.get(units.items[0]) orelse { + // This dependency loop is actually unreferenced, so we don't need to emit a compile + // error at all! Move onto the next dependency loop. + continue; + }; + while (opt_ref) |ref| : (opt_ref = all_references.get(ref.referencer).?) depth += 1; + break :depth depth; + }; + for (units.items[1..], 1..) |unit, index| { + var depth: u32 = 0; + var opt_ref = all_references.get(unit).?; + while (opt_ref) |ref| : (opt_ref = all_references.get(ref.referencer).?) depth += 1; + if (depth < start_depth) { + start_index = index; + start_depth = depth; + } + } + + // Collect a reference trace for the start of the loop. + var ref_trace: std.ArrayList(std.zig.ErrorBundle.ReferenceTrace) = .empty; + defer ref_trace.deinit(gpa); + const frame_limit = zcu.comp.reference_trace orelse 0; + try zcu.populateReferenceTrace(units.items[start_index], frame_limit, eb, &ref_trace); + + // Collect all notes first so we don't leave an incomplete root error message on `error.AlreadyReported`. + const note_buf = try gpa.alloc(std.zig.ErrorBundle.MessageIndex, units.items.len + 1); + defer gpa.free(note_buf); + note_buf[0] = addDependencyLoopNote(zcu, eb, units.items[start_index], ref_trace.items) catch |err| switch (err) { + error.AlreadyReported => return, // give up on the dep loop error + error.OutOfMemory => |e| return e, + }; + for (units.items[start_index + 1 ..], note_buf[1 .. units.items.len - start_index]) |unit, *note| { + note.* = addDependencyLoopNote(zcu, eb, unit, &.{}) catch |err| switch (err) { + error.AlreadyReported => return, // give up on the dep loop error + error.OutOfMemory => |e| return e, + }; + } + for (units.items[0..start_index], note_buf[units.items.len - start_index .. units.items.len]) |unit, *note| { + note.* = addDependencyLoopNote(zcu, eb, unit, &.{}) catch |err| switch (err) { + error.AlreadyReported => return, // give up on the dep loop error + error.OutOfMemory => |e| return e, + }; + } + note_buf[units.items.len] = try eb.addErrorMessage(.{ + .msg = try eb.addString("eliminate any one of these dependencies to break the loop"), + .src_loc = .none, + }); + + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("dependency loop with length {d}", .{units.items.len}), + .src_loc = .none, + .notes_len = @intCast(units.items.len + 1), + }); + const notes_start = try eb.reserveNotes(@intCast(units.items.len + 1)); + const notes: []std.zig.ErrorBundle.MessageIndex = @ptrCast(eb.extra.items[notes_start..]); + @memcpy(notes, note_buf); + } +} +fn addDependencyLoopNote( + zcu: *Zcu, + eb: *std.zig.ErrorBundle.Wip, + source_unit: AnalUnit, + ref_trace: []const std.zig.ErrorBundle.ReferenceTrace, +) (Allocator.Error || error{AlreadyReported})!std.zig.ErrorBundle.MessageIndex { + const ip = &zcu.intern_pool; + const comp = zcu.comp; + + const fmt_source: std.fmt.Alt(FormatAnalUnit, formatDependencyLoopSourceUnit) = .{ .data = .{ + .unit = source_unit, + .zcu = zcu, + } }; + + const dep_node = zcu.dependency_loop_nodes.get(source_unit).?; + + const msg: std.zig.ErrorBundle.String = switch (dep_node.unit.unwrap()) { + .@"comptime" => unreachable, // cannot be involved in a dependency loop + .nav_val => |nav| try eb.printString("{f} uses value of declaration '{f}' here", .{ + fmt_source, ip.getNav(nav).fqn.fmt(ip), + }), + .nav_ty => |nav| try eb.printString("{f} uses type of declaration '{f}' here", .{ + fmt_source, ip.getNav(nav).fqn.fmt(ip), + }), + .memoized_state => |stage| switch (stage) { + .panic => try eb.printString("{f} requires panic handler for call here", .{fmt_source}), + else => try eb.printString("{f} requires 'std.builtin' declarations here", .{fmt_source}), + }, + .func => |func| try eb.printString("{f} uses inferred error set of function '{f}' here", .{ + fmt_source, ip.getNav(zcu.funcInfo(func).owner_nav).fqn.fmt(ip), + }), + .type_layout => |ty| try eb.printString("{f} depends on type '{f}' {s}", .{ + fmt_source, + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + dep_node.reason.type_layout_reason.msg(), + }), + }; + + const src_loc = dep_node.reason.src.upgrade(zcu); + const source = src_loc.file_scope.getSource(zcu) catch |err| { + try Compilation.unableToLoadZcuFile(zcu, eb, src_loc.file_scope, err); + return error.AlreadyReported; + }; + const span = src_loc.span(zcu) catch |err| { + try Compilation.unableToLoadZcuFile(zcu, eb, src_loc.file_scope, err); + return error.AlreadyReported; + }; + const loc = std.zig.findLineColumn(source, span.main); + const eb_src = try eb.addSourceLocation(.{ + .src_path = try eb.printString("{f}", .{src_loc.file_scope.path.fmt(comp)}), + .span_start = span.start, + .span_main = span.main, + .span_end = span.end, + .line = @intCast(loc.line), + .column = @intCast(loc.column), + .source_line = try eb.addString(loc.source_line), + .reference_trace_len = @intCast(ref_trace.len), + }); + for (ref_trace) |rt| try eb.addReferenceTrace(rt); + return eb.addErrorMessage(.{ + .msg = msg, + .src_loc = eb_src, + }); +} +fn formatDependencyLoopSourceUnit(data: FormatAnalUnit, w: *Io.Writer) Io.Writer.Error!void { + const zcu = data.zcu; + const ip = &zcu.intern_pool; + switch (data.unit.unwrap()) { + .@"comptime" => unreachable, // cannot be involved in a dependency loop + .nav_val => |nav| try w.print("value of declaration '{f}'", .{ip.getNav(nav).fqn.fmt(ip)}), + .nav_ty => |nav| try w.print("type of declaration '{f}'", .{ip.getNav(nav).fqn.fmt(ip)}), + .memoized_state => |stage| switch (stage) { + .panic => try w.writeAll("panic handler"), + else => try w.writeAll("'std.builtin' declarations"), + }, + .type_layout => |ty| try w.print("type '{f}'", .{ + Type.fromInterned(ty).containerTypeName(ip).fmt(ip), + }), + .func => |func| try w.print("function '{f}'", .{ + ip.getNav(zcu.funcInfo(func).owner_nav).fqn.fmt(ip), + }), + } +} + +pub fn populateReferenceTrace( + zcu: *Zcu, + root: AnalUnit, + frame_limit: u32, + eb: *std.zig.ErrorBundle.Wip, + ref_trace: *std.ArrayList(std.zig.ErrorBundle.ReferenceTrace), +) Allocator.Error!void { + const ip = &zcu.intern_pool; + const gpa = zcu.comp.gpa; + + if (frame_limit == 0) return; + + const all_references = try zcu.resolveReferences(); + + var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .empty; + defer seen.deinit(gpa); + + var referenced_by = root; + while (all_references.get(referenced_by)) |maybe_ref| { + const ref = maybe_ref orelse break; + const gop = try seen.getOrPut(gpa, ref.referencer); + if (gop.found_existing) break; + if (ref_trace.items.len < frame_limit) { + var last_call_src = ref.src; + var opt_inline_frame = ref.inline_frame; + while (opt_inline_frame.unwrap()) |inline_frame| { + const f = inline_frame.ptr(zcu).*; + const func_nav = ip.indexToKey(f.callee).func.owner_nav; + const func_name = ip.getNav(func_nav).name.toSlice(ip); + addReferenceTraceFrame(zcu, eb, ref_trace, func_name, last_call_src, true) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.AlreadyReported => { + // An incomplete reference trace isn't the end of the world; just cut it off. + return; + }, + }; + last_call_src = f.call_src; + opt_inline_frame = f.parent; + } + const root_name: ?[]const u8 = switch (ref.referencer.unwrap()) { + .@"comptime" => "comptime", + .nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip), + .type_layout => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), + .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip), + .memoized_state => null, + }; + if (root_name) |n| { + addReferenceTraceFrame(zcu, eb, ref_trace, n, last_call_src, false) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.AlreadyReported => { + // An incomplete reference trace isn't the end of the world; just cut it off. + return; + }, + }; + } + } + referenced_by = ref.referencer; + } + + if (seen.count() > ref_trace.items.len) { + try ref_trace.append(gpa, .{ + .decl_name = @intCast(seen.count() - ref_trace.items.len), + .src_loc = .none, + }); + } +} +fn addReferenceTraceFrame( + zcu: *Zcu, + eb: *std.zig.ErrorBundle.Wip, + ref_trace: *std.ArrayList(std.zig.ErrorBundle.ReferenceTrace), + name: []const u8, + lazy_src: Zcu.LazySrcLoc, + inlined: bool, +) error{ OutOfMemory, AlreadyReported }!void { + const gpa = zcu.gpa; + const src = lazy_src.upgrade(zcu); + const source = src.file_scope.getSource(zcu) catch |err| { + try Compilation.unableToLoadZcuFile(zcu, eb, src.file_scope, err); + return error.AlreadyReported; + }; + const span = src.span(zcu) catch |err| { + try Compilation.unableToLoadZcuFile(zcu, eb, src.file_scope, err); + return error.AlreadyReported; + }; + const loc = std.zig.findLineColumn(source, span.main); + try ref_trace.append(gpa, .{ + .decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.printString("{f}", .{src.file_scope.path.fmt(zcu.comp)}), + .span_start = span.start, + .span_main = span.main, + .span_end = span.end, + .line = @intCast(loc.line), + .column = @intCast(loc.column), + .source_line = 0, + }), + }); +} + const TrackedUnitSema = struct { /// `null` means we created the node, so should end it. old_name: ?[std.Progress.Node.max_name_len]u8, diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 8d6af9c3ad..954d1cd3bf 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -728,7 +728,12 @@ pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Alloca /// 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. -pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.SemaError!void { +pub fn ensureMemoizedStateUpToDate( + pt: Zcu.PerThread, + stage: InternPool.MemoizedStateStage, + /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. + reason: ?*const Zcu.DependencyReason, +) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); @@ -748,12 +753,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(unit); // No need for `deleteUnitExports` because we never export anything. - zcu.deleteUnitReferences(unit); - zcu.deleteUnitCompileLogs(unit); - if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(unit); + zcu.resetUnit(unit); } else { if (prev_failed) return error.AnalysisFail; // We use an arbitrary element to check if the state has been resolved yet. @@ -772,7 +772,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized info.deps.clearRetainingCapacity(); } - const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed| + 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: { @@ -805,14 +805,18 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized if (new_failed) return error.AnalysisFail; } -fn analyzeMemoizedState(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.CompileError!bool { +fn analyzeMemoizedState( + pt: Zcu.PerThread, + stage: InternPool.MemoizedStateStage, + reason: ?*const Zcu.DependencyReason, +) Zcu.CompileError!bool { const zcu = pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; const unit: AnalUnit = .wrap(.{ .memoized_state = stage }); - try zcu.analysis_in_progress.putNoClobber(gpa, unit, {}); + try zcu.analysis_in_progress.putNoClobber(gpa, unit, reason); defer assert(zcu.analysis_in_progress.swapRemove(unit)); var analysis_arena: std.heap.ArenaAllocator = .init(gpa); @@ -871,13 +875,7 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU // `was_outdated` can be true in the initial update for comptime units, so this isn't a `dev.check`. if (dev.env.supports(.incremental)) { zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - zcu.deleteUnitCompileLogs(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + zcu.resetUnit(anal_unit); } } else { // We can trust the current information about this unit. @@ -943,7 +941,7 @@ fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu const file = zcu.fileByIndex(inst_resolved.file); const zir = file.zir.?; - try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, {}); + try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, null); defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); var analysis_arena: std.heap.ArenaAllocator = .init(gpa); @@ -1009,7 +1007,12 @@ fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu /// re-analysis if necessary. Asserts that `ty` is a struct (not a tuple!) or union. Returns /// `error.AnalysisFail` if an analysis error is encountered during type resolution; the caller is /// free to ignore this, since the error is already registered. -pub fn ensureTypeLayoutUpToDate(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void { +pub fn ensureTypeLayoutUpToDate( + pt: Zcu.PerThread, + ty: Type, + /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. + reason: ?*const Zcu.DependencyReason, +) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1031,13 +1034,7 @@ pub fn ensureTypeLayoutUpToDate(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void // `was_outdated` is true in the initial update, so this isn't a `dev.check`. if (dev.env.supports(.incremental)) { zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - zcu.deleteUnitCompileLogs(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + zcu.resetUnit(anal_unit); } // For types, we already know that we have to invalidate all dependees. // TODO: we actually *could* detect whether everything was the same. should we bother? @@ -1058,7 +1055,7 @@ pub fn ensureTypeLayoutUpToDate(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void const unit_tracking = zcu.trackUnitSema(ty.containerTypeName(&zcu.intern_pool).toSlice(&zcu.intern_pool), null); defer unit_tracking.end(zcu); - try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + try zcu.analysis_in_progress.put(gpa, anal_unit, reason); defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); var analysis_arena: std.heap.ArenaAllocator = .init(gpa); @@ -1114,7 +1111,12 @@ pub fn ensureTypeLayoutUpToDate(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void /// Ensures that the resolved value of the given `Nav` 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 ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { +pub fn ensureNavValUpToDate( + pt: Zcu.PerThread, + 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 { const tracy = trace(@src()); defer tracy.end(); @@ -1150,13 +1152,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - zcu.deleteUnitCompileLogs(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - ip.removeDependenciesForDepender(gpa, anal_unit); + zcu.resetUnit(anal_unit); } else { // We can trust the current information about this unit. if (prev_failed) return error.AnalysisFail; @@ -1173,7 +1169,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu const unit_tracking = zcu.trackUnitSema(nav.fqn.toSlice(ip), nav.srcInst(ip)); defer unit_tracking.end(zcu); - const invalidate_value: bool, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { + const invalidate_value: bool, const new_failed: bool = if (pt.analyzeNavVal(nav_id, reason)) |result| res: { break :res .{ // If the unit has gone from failed to success, we still need to invalidate the dependencies. result.val_changed or prev_failed, @@ -1217,39 +1213,14 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu } } - // If there isn't a type annotation, then we have also just resolved the type. That means the - // the type is up-to-date, so it won't have the chance to mark its own dependency on the value; - // we must do that ourselves. - type_deps_on_val: { - const inst_resolved = nav.analysis.?.zir_index.resolveFull(ip) orelse break :type_deps_on_val; - const file = zcu.fileByIndex(inst_resolved.file); - const zir_decl = file.zir.?.getDeclaration(inst_resolved.inst); - if (zir_decl.type_body != null) break :type_deps_on_val; - // The type does indeed depend on the value. We are responsible for populating all state of - // the `nav_ty`, including exports, references, errors, and dependencies. - const ty_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id }); - const ty_was_outdated = zcu.outdated.swapRemove(ty_unit) or - zcu.potentially_outdated.swapRemove(ty_unit); - if (ty_was_outdated) { - _ = zcu.outdated_ready.swapRemove(ty_unit); - zcu.deleteUnitExports(ty_unit); - zcu.deleteUnitReferences(ty_unit); - zcu.deleteUnitCompileLogs(ty_unit); - if (zcu.failed_analysis.fetchSwapRemove(ty_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(ty_unit); - ip.removeDependenciesForDepender(gpa, ty_unit); - } - try pt.addDependency(ty_unit, .{ .nav_val = nav_id }); - if (new_failed) try zcu.transitive_failed_analysis.put(gpa, ty_unit, {}); - if (ty_was_outdated) try zcu.markDependeeOutdated(.marked_po, .{ .nav_ty = nav_id }); - } - if (new_failed) return error.AnalysisFail; } -fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { val_changed: bool } { +fn analyzeNavVal( + pt: Zcu.PerThread, + nav_id: InternPool.Nav.Index, + reason: ?*const Zcu.DependencyReason, +) Zcu.CompileError!struct { val_changed: bool } { const zcu = pt.zcu; const ip = &zcu.intern_pool; const comp = zcu.comp; @@ -1266,17 +1237,9 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr const zir = file.zir.?; const zir_decl = zir.getDeclaration(inst_resolved.inst); - try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, {}); + try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, reason); errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - // If there's no type body, we are also resolving the type here. - if (zir_decl.type_body == null) { - try zcu.analysis_in_progress.putNoClobber(gpa, .wrap(.{ .nav_ty = nav_id }), {}); - } - errdefer if (zir_decl.type_body == null) { - _ = zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id })); - }; - var analysis_arena: std.heap.ArenaAllocator = .init(gpa); defer analysis_arena.deinit(); @@ -1352,9 +1315,6 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr const nav_ty: Type = maybe_ty orelse final_val.?.typeOf(zcu); - // First, we must resolve the declaration's type. To do this, we analyze the type body if available, - // or otherwise, we analyze the value body, populating `early_val` in the process. - const is_const = is_const: switch (zir_decl.kind) { .@"comptime" => unreachable, // this is not a Nav .unnamed_test, .@"test", .decltest => { @@ -1441,7 +1401,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr // This resolves the type of the resolved value, not that value itself. If `nav_val` is a struct type, // this resolves the type `type` (which needs no resolution), not the struct itself. - try sema.ensureLayoutResolved(nav_ty, init_src); + try sema.ensureLayoutResolved(nav_ty, block.nodeOffset(.zero), if (zir_decl.kind == .@"var") .variable else .constant); const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { .func => |f| .{ true, f.owner_nav == nav_id }, // note that this lets function aliases reach codegen @@ -1460,18 +1420,18 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr } } else if (nav_ty.comptimeOnly(zcu)) { // alignment, linksection, addrspace annotations are not allowed for comptime-only types. - const reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) { + const cannot_align_reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) { .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* else => "comptime-only type", }; if (zir_decl.align_body != null) { - return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); + return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{cannot_align_reason}); } if (zir_decl.linksection_body != null) { - return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); + return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{cannot_align_reason}); } if (zir_decl.addrspace_body != null) { - return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); + return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{cannot_align_reason}); } } @@ -1484,10 +1444,8 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr }); // Mark the unit as completed before evaluating the export! + // MLUGG TODO: do we really need to do this? assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - if (zir_decl.type_body == null) { - assert(zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id }))); - } if (zir_decl.linkage == .@"export") { const export_src = block.src(.{ .token_offset = @enumFromInt(@intFromBool(zir_decl.is_pub)) }); @@ -1516,7 +1474,12 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr } } -pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { +pub fn ensureNavTypeUpToDate( + pt: Zcu.PerThread, + 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 { const tracy = trace(@src()); defer tracy.end(); @@ -1533,18 +1496,6 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc try zcu.ensureNavValAnalysisQueued(nav_id); - const type_resolved_by_value: bool = from_val: { - const analysis = nav.analysis orelse break :from_val false; - const inst_resolved = analysis.zir_index.resolveFull(ip) orelse break :from_val false; - const file = zcu.fileByIndex(inst_resolved.file); - const zir_decl = file.zir.?.getDeclaration(inst_resolved.inst); - break :from_val zir_decl.type_body == null; - }; - if (type_resolved_by_value) { - // Logic at the end of `ensureNavValUpToDate` is directly responsible for populating our state. - return pt.ensureNavValUpToDate(nav_id); - } - // Determine whether or not this `Nav`'s type is outdated. This also includes checking if the // status is `.unresolved`, which indicates that the value is outdated because it has *never* // been analyzed so far. @@ -1564,13 +1515,7 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - zcu.deleteUnitCompileLogs(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - ip.removeDependenciesForDepender(gpa, anal_unit); + zcu.resetUnit(anal_unit); } else { // We can trust the current information about this unit. if (prev_failed) return error.AnalysisFail; @@ -1587,7 +1532,7 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc const unit_tracking = zcu.trackUnitSema(nav.fqn.toSlice(ip), nav.srcInst(ip)); defer unit_tracking.end(zcu); - const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id)) |result| res: { + const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id, reason)) |result| res: { break :res .{ // If the unit has gone from failed to success, we still need to invalidate the dependencies. result.type_changed or prev_failed, @@ -1634,7 +1579,11 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc if (new_failed) return error.AnalysisFail; } -fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { type_changed: bool } { +fn analyzeNavType( + pt: Zcu.PerThread, + nav_id: InternPool.Nav.Index, + reason: ?*const Zcu.DependencyReason, +) Zcu.CompileError!struct { type_changed: bool } { const zcu = pt.zcu; const comp = zcu.comp; const gpa = comp.gpa; @@ -1650,11 +1599,10 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr const file = zcu.fileByIndex(inst_resolved.file); const zir = file.zir.?; - try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, {}); + try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, reason); defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); const zir_decl = zir.getDeclaration(inst_resolved.inst); - const type_body = zir_decl.type_body.?; var analysis_arena: std.heap.ArenaAllocator = .init(gpa); defer analysis_arena.deinit(); @@ -1696,6 +1644,17 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr defer block.instructions.deinit(gpa); const ty_src = block.src(.{ .node_offset_var_decl_ty = .zero }); + const init_src = block.src(.{ .node_offset_var_decl_init = .zero }); + + const type_body = zir_decl.type_body orelse { + // There is no type annotation, so we just need to use the declaration's value. + try sema.ensureNavResolved(&block, init_src, nav_id, .fully); + // We don't actually know what the type of this Nav was before it was resolved, so we just + // have to assume we were outdated. This isn't too bad, because assuming there was also no + // type annotation last update, we should only be re-analyzed if the value changes (it's our + // only dependency), or if there was a dependency loop. + return .{ .type_changed = true }; + }; block.comptime_reason = .{ .reason = .{ .src = ty_src, @@ -1708,7 +1667,7 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr break :ty .fromInterned(type_ref.toInterned().?); }; - try sema.ensureLayoutResolved(resolved_ty, ty_src); + try sema.ensureLayoutResolved(resolved_ty, block.nodeOffset(.zero), if (zir_decl.kind == .@"var") .variable else .constant); // In the case where the type is specified, this function is also responsible for resolving // the pointer modifiers, i.e. alignment, linksection, addrspace. @@ -1758,7 +1717,12 @@ fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileEr return .{ .type_changed = true }; } -pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!void { +pub fn ensureFuncBodyUpToDate( + pt: Zcu.PerThread, + 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 { dev.check(.sema); const tracy = trace(@src()); @@ -1788,12 +1752,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, func_index: InternPool.Index) Z dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - zcu.deleteUnitCompileLogs(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + zcu.resetUnit(anal_unit); } else { // We can trust the current information about this function. if (prev_failed) return error.AnalysisFail; @@ -1813,7 +1772,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, func_index: InternPool.Index) Z ); defer unit_tracking.end(zcu); - const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result| + 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: { @@ -1854,6 +1813,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, func_index: InternPool.Index) Z fn analyzeFuncBody( pt: Zcu.PerThread, func_index: InternPool.Index, + reason: ?*const Zcu.DependencyReason, ) Zcu.SemaError!struct { ies_outdated: bool } { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -1868,7 +1828,7 @@ fn analyzeFuncBody( if (func.generic_owner == .none) { // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureNavValUpToDate(func.owner_nav); + try pt.ensureNavValUpToDate(func.owner_nav, reason); if (ip.getNav(func.owner_nav).status.fully_resolved.val != func_index) { // This function is no longer referenced! There's no point in re-analyzing it. // Just mark a transitive failure and move on. @@ -1877,7 +1837,7 @@ fn analyzeFuncBody( } else { const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureNavValUpToDate(go_nav); + try pt.ensureNavValUpToDate(go_nav, reason); if (ip.getNav(go_nav).status.fully_resolved.val != func.generic_owner) { // The generic owner is no longer referenced, so this function is also unreferenced. // There's no point in re-analyzing it. Just mark a transitive failure and move on. @@ -1894,7 +1854,7 @@ fn analyzeFuncBody( log.debug("analyze and generate fn body {f}", .{zcu.fmtAnalUnit(anal_unit)}); - var air = try pt.analyzeFuncBodyInner(func_index); + var air = try pt.analyzeFuncBodyInner(func_index, reason); errdefer air.deinit(gpa); const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or @@ -2842,7 +2802,11 @@ const ScanDeclIter = struct { } }; -fn analyzeFuncBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { +fn analyzeFuncBodyInner( + pt: Zcu.PerThread, + func_index: InternPool.Index, + reason: ?*const Zcu.DependencyReason, +) Zcu.SemaError!Air { const tracy = trace(@src()); defer tracy.end(); @@ -2858,7 +2822,7 @@ fn analyzeFuncBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.Sem const file = zcu.fileByIndex(inst_info.file); const zir = file.zir.?; - try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, {}); + try zcu.analysis_in_progress.putNoClobber(gpa, anal_unit, reason); defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); if (func.analysisUnordered(ip).inferred_error_set) { @@ -2879,8 +2843,6 @@ fn analyzeFuncBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.Sem const func_nav = ip.getNav(func.owner_nav); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - var analysis_arena = std.heap.ArenaAllocator.init(gpa); defer analysis_arena.deinit(); @@ -2978,7 +2940,7 @@ fn analyzeFuncBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.Sem const param_ty: Type = .fromInterned(fn_ty_info.param_types.get(ip)[runtime_param_index]); runtime_param_index += 1; - try sema.ensureLayoutResolved(param_ty, inner_block.src(.{ .func_decl_param_ty = @intCast(zir_param_index) })); + try sema.ensureLayoutResolved(param_ty, inner_block.src(.{ .func_decl_param_ty = @intCast(zir_param_index) }), .parameter); if (try param_ty.onePossibleValue(pt)) |opv| { gop.value_ptr.* = .fromValue(opv); continue; @@ -2995,7 +2957,7 @@ fn analyzeFuncBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.Sem }); } - try sema.ensureLayoutResolved(sema.fn_ret_ty, inner_block.src(.{ .node_offset_fn_type_ret_ty = .zero })); + try sema.ensureLayoutResolved(sema.fn_ret_ty, inner_block.src(.{ .node_offset_fn_type_ret_ty = .zero }), .return_type); const last_arg_index = inner_block.instructions.items.len; @@ -4206,7 +4168,7 @@ pub fn resolveTypeForCodegen(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void { }, .@"struct" => switch (ip.indexToKey(ty.toIntern())) { - .struct_type => try pt.ensureTypeLayoutUpToDate(ty), + .struct_type => try pt.ensureTypeLayoutUpToDate(ty, null), .tuple_type => |tuple| for (0..tuple.types.len) |i| { const field_is_comptime = tuple.values.get(ip)[i] != .none; if (field_is_comptime) continue; @@ -4216,8 +4178,8 @@ pub fn resolveTypeForCodegen(pt: Zcu.PerThread, ty: Type) Zcu.SemaError!void { else => unreachable, }, - .@"union" => try pt.ensureTypeLayoutUpToDate(ty), - .@"enum" => try pt.ensureTypeLayoutUpToDate(ty), + .@"union" => try pt.ensureTypeLayoutUpToDate(ty, null), + .@"enum" => try pt.ensureTypeLayoutUpToDate(ty, null), } } pub fn resolveValueTypesForCodegen(pt: Zcu.PerThread, val: Value) Zcu.SemaError!void { diff --git a/test/incremental/type_dependency_loop b/test/incremental/type_dependency_loop new file mode 100644 index 0000000000..40f41bf19f --- /dev/null +++ b/test/incremental/type_dependency_loop @@ -0,0 +1,55 @@ +#target=x86_64-linux-selfhosted +#target=x86_64-windows-selfhosted +#target=x86_64-linux-cbe +#target=x86_64-windows-cbe +#target=wasm32-wasi-selfhosted +#update=initial version +#file=main.zig +pub const A = struct { b: B }; +pub const B = struct { a: A }; +pub fn main() void { + _ = @as(B, undefined); +} +#expect_error=:error: dependency loop with length 2 +#expect_error=main.zig:2:27: note: type 'main.B' depends on type 'main.A' for field declared here +#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here +#expect_error=:note: eliminate any one of these dependencies to break the loop + +#update=remove reference to dependency loop +#file=main.zig +pub const A = struct { b: B }; +pub const B = struct { a: A }; +pub fn main() void { + _ = B; +} +#expect_stdout="" + +#update=change dependency loop without fixing it +#file=main.zig +pub const A = struct { b: B }; +pub const B = struct { a: *align(@alignOf(A)) A }; +pub fn main() void { + _ = B; +} +#expect_stdout="" + +#update=reference dependency loop again +#file=main.zig +pub const A = struct { b: B }; +pub const B = struct { a: *align(@alignOf(A)) A }; +pub fn main() void { + _ = @as(B, undefined); +} +#expect_error=:error: dependency loop with length 2 +#expect_error=main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here +#expect_error=main.zig:1:27: note: type 'main.A' depends on type 'main.B' for field declared here +#expect_error=:note: eliminate any one of these dependencies to break the loop + +#update=fix dependency loop +#file=main.zig +pub const A = struct { b: B }; +pub const B = struct { a: *A }; +pub fn main() void { + _ = @as(B, undefined); +} +#expect_stdout="" diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 171af57036..87a782c2d3 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -417,29 +417,32 @@ const Eval = struct { is_note: bool, err_idx: std.zig.ErrorBundle.MessageIndex, ) Allocator.Error!void { + const io = eval.io; const err = eb.getErrorMessage(err_idx); - if (err.src_loc == .none) @panic("TODO error message with no source location"); if (err.count != 1) @panic("TODO error message with count>1"); const msg = eb.nullTerminatedString(err.msg); - const src = eb.getSourceLocation(err.src_loc); - const raw_filename = eb.nullTerminatedString(src.src_path); - - const io = eval.io; - - // We need to replace backslashes for consistency between platforms. - const filename = name: { - if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename; - const copied = try eval.arena.dupe(u8, raw_filename); - std.mem.replaceScalar(u8, copied, '\\', '/'); - break :name copied; + const matches = matches: { + if (expected.is_note != is_note) break :matches false; + if (!std.mem.eql(u8, expected.msg, msg)) break :matches false; + if (err.src_loc == .none) { + break :matches expected.src == null; + } + const expected_src = expected.src orelse break :matches false; + const src = eb.getSourceLocation(err.src_loc); + const raw_filename = eb.nullTerminatedString(src.src_path); + // We need to replace backslashes for consistency between platforms. + const filename = name: { + if (std.mem.indexOfScalar(u8, raw_filename, '\\') == null) break :name raw_filename; + const copied = try eval.arena.dupe(u8, raw_filename); + std.mem.replaceScalar(u8, copied, '\\', '/'); + break :name copied; + }; + if (!std.mem.eql(u8, expected_src.filename, filename)) break :matches false; + if (expected_src.line != src.line + 1) break :matches false; + if (expected_src.column != src.column + 1) break :matches false; + break :matches true; }; - - if (expected.is_note != is_note or - !std.mem.eql(u8, expected.filename, filename) or - expected.line != src.line + 1 or - expected.column != src.column + 1 or - !std.mem.eql(u8, expected.msg, msg)) - { + if (!matches) { eb.renderToStderr(io, .{}, .auto) catch {}; eval.fatal("compile error did not match expected error", .{}); } @@ -714,10 +717,12 @@ const Case = struct { const ExpectedError = struct { is_note: bool, - filename: []const u8, - line: u32, - column: u32, msg: []const u8, + src: ?struct { + filename: []const u8, + line: u32, + column: u32, + }, }; fn parse(arena: Allocator, io: Io, bytes: []const u8) !Case { @@ -930,16 +935,16 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { var it = std.mem.splitScalar(u8, str, ':'); const filename = it.first(); - const line_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l}); - const column_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l}); + const line_str, const column_str = if (filename.len > 0) .{ + it.next() orelse fatal("line {d}: incomplete error specification", .{l}), + it.next() orelse fatal("line {d}: incomplete error specification", .{l}), + } else .{ undefined, undefined }; const error_or_note_str = std.mem.trim( u8, it.next() orelse fatal("line {d}: incomplete error specification", .{l}), " ", ); - const message = std.mem.trim(u8, it.rest(), " "); - if (filename.len == 0) fatal("line {d}: empty filename", .{l}); - if (message.len == 0) fatal("line {d}: empty error message", .{l}); + const is_note = if (std.mem.eql(u8, error_or_note_str, "error")) false else if (std.mem.eql(u8, error_or_note_str, "note")) @@ -947,18 +952,19 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { else fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str }); - const line = std.fmt.parseInt(u32, line_str, 10) catch - fatal("line {d}: invalid line number '{s}'", .{ l, line_str }); - - const column = std.fmt.parseInt(u32, column_str, 10) catch - fatal("line {d}: invalid column number '{s}'", .{ l, column_str }); + const message = std.mem.trim(u8, it.rest(), " "); + if (message.len == 0) fatal("line {d}: empty error message", .{l}); return .{ .is_note = is_note, - .filename = filename, - .line = line, - .column = column, .msg = message, + .src = if (filename.len == 0) null else .{ + .filename = filename, + .line = std.fmt.parseInt(u32, line_str, 10) catch + fatal("line {d}: invalid line number '{s}'", .{ l, line_str }), + .column = std.fmt.parseInt(u32, column_str, 10) catch + fatal("line {d}: invalid column number '{s}'", .{ l, column_str }), + }, }; }