From 334189ce6d20d6d1100f115252d5589fadb064b1 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 27 Jan 2026 11:46:48 +0000 Subject: [PATCH] compiler: simplify IESes It is always a bug in Sema to check whether an IES is resolved. This is because whether the IES is resolved depends on whether the function which owns it has been analyzed yet, which depends on the order the compiler analyzes declarations in, which it is incorrect to have any dependency on. Instead, we must always either not look at the resolved set, or resolve it first (with `Sema.ensureFuncIesResolved`) and then look at the definitely-resolved concrete error set. Luckily, removing a bunch of the buggy logic which tried to opportunistically use already-resolved inferred error sets actually didn't regress anything! It seems this logic was mostly left over from before Andrew reworked inferred error sets, and had become essentially dead code. This is because inferred error sets are stricter than they used to be, and in particular, we make no attempt to support mutual recursion. I suspect that most of the logic touching IESes can be simplified even further than I have done here without regressing any existing code; my goal in this commit was just to remove any *buggy* code I could find. --- src/Sema.zig | 560 ++++++++++++++--------------------- src/Sema/type_resolution.zig | 10 +- src/Type.zig | 11 +- src/Value.zig | 4 +- src/Zcu.zig | 27 +- 5 files changed, 232 insertions(+), 380 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 09c974fb256e4709030a8e6540e468140af8131c..2fffe8ae546af74951980c583506ba82647b0d60 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7870,21 +7870,21 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr return .anyerror_type; } - if (ip.isInferredErrorSetType(lhs_ty.toIntern())) { - switch (try sema.resolveInferredErrorSet(block, src, lhs_ty.toIntern())) { - // isAnyError might have changed from a false negative to a true - // positive after resolution. - .anyerror_type => return .anyerror_type, - else => {}, - } + switch (ip.indexToKey(lhs_ty.toIntern())) { + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src, func_index); + if (ip.funcIesResolvedUnordered(func_index) == .anyerror_type) return .anyerror_type; + }, + .error_set_type => {}, + else => unreachable, } - if (ip.isInferredErrorSetType(rhs_ty.toIntern())) { - switch (try sema.resolveInferredErrorSet(block, src, rhs_ty.toIntern())) { - // isAnyError might have changed from a false negative to a true - // positive after resolution. - .anyerror_type => return .anyerror_type, - else => {}, - } + switch (ip.indexToKey(rhs_ty.toIntern())) { + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src, func_index); + if (ip.funcIesResolvedUnordered(func_index) == .anyerror_type) return .anyerror_type; + }, + .error_set_type => {}, + else => unreachable, } const err_set_ty = try sema.errorSetMerge(lhs_ty, rhs_ty); @@ -12000,7 +12000,7 @@ fn wantSwitchProngBodyAnalysis( if (err_set and prong_is_comptime_unreach) { const item_val = sema.resolveConstDefinedValue(block, .unneeded, item_ref, undefined) catch unreachable; const err_name = item_val.getErrorName(zcu).unwrap().?; - if (!Type.errorSetHasFieldIp(&zcu.intern_pool, operand_ty.toIntern(), err_name)) return false; + if (!operand_ty.errorSetHasField(err_name, zcu)) return false; } return true; } @@ -21023,34 +21023,61 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData else => unreachable, }; - const disjoint = disjoint: { - // Try avoiding resolving inferred error sets if we can - if (!dest_err_ty.isAnyError(zcu) and dest_err_ty.errorSetIsEmpty(zcu)) break :disjoint true; - if (!operand_err_ty.isAnyError(zcu) and operand_err_ty.errorSetIsEmpty(zcu)) break :disjoint true; - if (dest_err_ty.isAnyError(zcu)) break :disjoint false; - if (operand_err_ty.isAnyError(zcu)) break :disjoint false; - const dest_err_names = dest_err_ty.errorSetNames(zcu); - for (0..dest_err_names.len) |dest_err_index| { - if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index])) - break :disjoint false; - } + switch (ip.indexToKey(operand_err_ty.toIntern())) { + .inferred_error_set_type => |func| try sema.ensureFuncIesResolved(block, src, func), + else => {}, + } - if (!ip.isInferredErrorSetType(dest_err_ty.toIntern()) and - !ip.isInferredErrorSetType(operand_err_ty.toIntern())) - { - break :disjoint true; - } - - _ = try sema.resolveInferredErrorSetTy(block, src, dest_err_ty.toIntern()); - _ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_err_ty.toIntern()); - for (0..dest_err_names.len) |dest_err_index| { - if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index])) - break :disjoint false; - } - - break :disjoint true; + const result: enum { + /// The operand and destination error sets are disjoint, i.e. have no errors in common. + disjoint, + /// The destination error set is a superset of the operand error set, so the operation is + /// effectively equivalent to a coercion. + superset, + /// The operand and destination error sets have *some* errors in common, but the destination + /// is not a superset of the operand, so a safety check may be needed. + overlap, + } = if (operand_err_ty.errorSetIsEmpty(zcu)) res: { + break :res .disjoint; + } else check: switch (dest_err_ty.toIntern()) { + .anyerror_type => .superset, + .adhoc_inferred_error_set_type => { + // `@errorCast` to this function's own error set. + try sema.fn_ret_ty_ies.?.addErrorSet(operand_err_ty, ip, sema.arena); + break :check .superset; + }, + else => |err_set_ty| switch (ip.indexToKey(err_set_ty)) { + .inferred_error_set_type => |func_index| { + if (sema.fn_ret_ty_ies) |dst_ies| { + if (dst_ies.func == func_index) { + // `@errorCast` to this function's own error set. + try sema.fn_ret_ty_ies.?.addErrorSet(operand_err_ty, ip, sema.arena); + break :check .superset; + } + } + try sema.ensureFuncIesResolved(block, src, func_index); + continue :check ip.funcIesResolvedUnordered(func_index); + }, + .error_set_type => |dest| { + if (operand_err_ty.isAnyError(zcu)) break :check .superset; + var dest_has_all = true; + var dest_has_any = false; + for (operand_err_ty.errorSetNames(zcu).get(ip)) |operand_err_name| { + if (dest.nameIndex(ip, operand_err_name) != null) { + dest_has_any = true; + } else { + dest_has_all = false; + } + } + if (!dest_has_any) break :check .disjoint; + if (dest_has_all) break :check .superset; + break :check .overlap; + }, + else => unreachable, + }, }; - if (disjoint and !(operand_tag == .error_union and dest_tag == .error_union)) { + + if (result == .disjoint and !(operand_tag == .error_union and dest_tag == .error_union)) { return sema.fail(block, src, "error sets '{f}' and '{f}' have no common errors", .{ operand_err_ty.fmt(pt), dest_err_ty.fmt(pt), }); @@ -21058,25 +21085,30 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData // operand must be defined since it can be an invalid error value if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| { - const err_name: InternPool.NullTerminatedString = switch (operand_tag) { - .error_set => ip.indexToKey(operand_val.toIntern()).err.name, - .error_union => switch (ip.indexToKey(operand_val.toIntern()).error_union.val) { + const err_name: InternPool.NullTerminatedString = switch (ip.indexToKey(operand_val.toIntern())) { + .err => |err| err.name, + .error_union => |eu| switch (eu.val) { .err_name => |name| name, .payload => |payload_val| { assert(dest_tag == .error_union); // should be guaranteed from the type checks above - return sema.coerce(block, dest_ty, Air.internedToRef(payload_val), operand_src); + const dest_payload_ty = dest_ty.errorUnionPayload(zcu); + const coerced_payload = try sema.coerce(block, dest_payload_ty, .fromIntern(payload_val), operand_src); + return sema.wrapErrorUnionPayload(block, dest_ty, coerced_payload, operand_src) catch |err| switch (err) { + error.NotCoercible => unreachable, + else => |e| return e, + }; }, }, else => unreachable, }; - if (!dest_err_ty.isAnyError(zcu) and !Type.errorSetHasFieldIp(ip, dest_err_ty.toIntern(), err_name)) { + if (!dest_err_ty.isAnyError(zcu) and !dest_err_ty.errorSetHasField(err_name, zcu)) { return sema.fail(block, src, "'error.{f}' not a member of error set '{f}'", .{ err_name.fmt(ip), dest_err_ty.fmt(pt), }); } - return Air.internedToRef(try pt.intern(switch (dest_tag) { + return .fromIntern(try pt.intern(switch (dest_tag) { .error_set => .{ .err = .{ .ty = dest_ty.toIntern(), .name = err_name, @@ -21090,21 +21122,17 @@ fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData } const err_int_ty = try pt.errorIntType(); - if (block.wantSafety() and !dest_err_ty.isAnyError(zcu) and - dest_err_ty.toIntern() != .adhoc_inferred_error_set_type and - zcu.backendSupportsFeature(.error_set_has_value)) - { + if (block.wantSafety() and result != .superset and zcu.backendSupportsFeature(.error_set_has_value)) { const err_code_inst = switch (operand_tag) { .error_set => operand, .error_union => try block.addTyOp(.unwrap_errunion_err, operand_err_ty, operand), else => unreachable, }; const err_int_inst = try block.addBitCast(err_int_ty, err_code_inst); - if (dest_tag == .error_union) { const zero_err = try pt.intRef(err_int_ty, 0); const is_zero = try block.addBinOp(.cmp_eq, err_int_inst, zero_err); - if (disjoint) { + if (result == .disjoint) { // Error must be zero. try sema.addSafetyCheck(block, src, is_zero, .invalid_error_code); } else { @@ -25599,31 +25627,28 @@ fn fieldVal( switch (child_type.zigTypeTag(zcu)) { .error_set => { - switch (ip.indexToKey(child_type.toIntern())) { - .error_set_type => |error_set_type| blk: { - if (error_set_type.nameIndex(ip, field_name) != null) break :blk; + const err_set_ty: Type = err_set: switch (ip.indexToKey(child_type.toIntern())) { + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src, func_index); + const resolved_ies = ip.funcIesResolvedUnordered(func_index); + continue :err_set ip.indexToKey(resolved_ies); + }, + .error_set_type => |err_set| if (err_set.nameIndex(ip, field_name) == null) { return sema.fail(block, src, "no error named '{f}' in '{f}'", .{ field_name.fmt(ip), child_type.fmt(pt), }); - }, - .inferred_error_set_type => { - return sema.fail(block, src, "TODO handle inferred error sets here", .{}); - }, + } else child_type, .simple_type => |t| { assert(t == .anyerror); _ = try pt.getErrorValue(field_name); + break :err_set try pt.singleErrorSetType(field_name); }, else => unreachable, - } - - const error_set_type = if (!child_type.isAnyError(zcu)) - child_type - else - try pt.singleErrorSetType(field_name); - return Air.internedToRef((try pt.intern(.{ .err = .{ - .ty = error_set_type.toIntern(), + }; + return .fromIntern(try pt.intern(.{ .err = .{ + .ty = err_set_ty.toIntern(), .name = field_name, - } }))); + } })); }, .@"union" => { if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| { @@ -25832,31 +25857,26 @@ fn fieldPtr( switch (child_type.zigTypeTag(zcu)) { .error_set => { - switch (ip.indexToKey(child_type.toIntern())) { - .error_set_type => |error_set_type| blk: { - if (error_set_type.nameIndex(ip, field_name) != null) { - break :blk; - } + const err_set_ty: Type = err_set: switch (ip.indexToKey(child_type.toIntern())) { + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src, func_index); + const resolved_ies = ip.funcIesResolvedUnordered(func_index); + continue :err_set ip.indexToKey(resolved_ies); + }, + .error_set_type => |err_set| if (err_set.nameIndex(ip, field_name) == null) { return sema.fail(block, src, "no error named '{f}' in '{f}'", .{ field_name.fmt(ip), child_type.fmt(pt), }); - }, - .inferred_error_set_type => { - return sema.fail(block, src, "TODO handle inferred error sets here", .{}); - }, + } else child_type, .simple_type => |t| { assert(t == .anyerror); _ = try pt.getErrorValue(field_name); + break :err_set try pt.singleErrorSetType(field_name); }, else => unreachable, - } - - const error_set_type = if (!child_type.isAnyError(zcu)) - child_type - else - try pt.singleErrorSetType(field_name); + }; return uavRef(sema, try pt.intern(.{ .err = .{ - .ty = error_set_type.toIntern(), + .ty = err_set_ty.toIntern(), .name = field_name, } })); }, @@ -27760,23 +27780,27 @@ fn coerceExtra( else => {}, }, .error_union => switch (inst_ty.zigTypeTag(zcu)) { - .error_set => { - // E to E!T - return sema.wrapErrorUnionSet(block, dest_ty, inst, inst_src); + // E to E!T + .error_set => if (sema.wrapErrorUnionSet(block, dest_ty, inst, inst_src)) |res| { + return res; + } else |err| switch (err) { + error.NotCoercible => if (in_memory_result == .no_match) { + // Try to give more useful notes + const err_set_type = dest_ty.errorUnionSet(zcu); + in_memory_result = try sema.coerceInMemoryAllowed(block, err_set_type, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val); + }, + else => |e| return e, }, - else => eu: { - // T to E!T - return sema.wrapErrorUnionPayload(block, dest_ty, inst, inst_src) catch |err| switch (err) { - error.NotCoercible => { - if (in_memory_result == .no_match) { - const payload_type = dest_ty.errorUnionPayload(zcu); - // Try to give more useful notes - in_memory_result = try sema.coerceInMemoryAllowed(block, payload_type, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val); - } - break :eu; - }, - else => |e| return e, - }; + // T to E!T + else => if (sema.wrapErrorUnionPayload(block, dest_ty, inst, inst_src)) |res| { + return res; + } else |err| switch (err) { + error.NotCoercible => if (in_memory_result == .no_match) { + // Try to give more useful notes + const payload_type = dest_ty.errorUnionPayload(zcu); + in_memory_result = try sema.coerceInMemoryAllowed(block, payload_type, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val); + }, + else => |e| return e, }, }, .@"union" => switch (inst_ty.zigTypeTag(zcu)) { @@ -28542,89 +28566,62 @@ fn coerceInMemoryAllowedErrorSets( const gpa = sema.gpa; const ip = &zcu.intern_pool; - // Coercion to `anyerror`. Note that this check can return false negatives - // in case the error sets did not get resolved. - if (dest_ty.isAnyError(zcu)) { - return .ok; - } - - if (dest_ty.toIntern() == .adhoc_inferred_error_set_type) { - // We are trying to coerce an error set to the current function's - // inferred error set. - const dst_ies = sema.fn_ret_ty_ies.?; - try dst_ies.addErrorSet(src_ty, ip, sema.arena); - return .ok; - } - - if (ip.isInferredErrorSetType(dest_ty.toIntern())) { - const dst_ies_func_index = ip.iesFuncIndex(dest_ty.toIntern()); - if (sema.fn_ret_ty_ies) |dst_ies| { - if (dst_ies.func == dst_ies_func_index) { - // We are trying to coerce an error set to the current function's - // inferred error set. - try dst_ies.addErrorSet(src_ty, ip, sema.arena); - return .ok; - } - } - switch (try sema.resolveInferredErrorSet(block, dest_src, dest_ty.toIntern())) { - // isAnyError might have changed from a false negative to a true - // positive after resolution. - .anyerror_type => return .ok, - else => {}, - } - } - - var missing_error_buf = std.array_list.Managed(InternPool.NullTerminatedString).init(gpa); - defer missing_error_buf.deinit(); - - switch (src_ty.toIntern()) { - .anyerror_type => switch (ip.indexToKey(dest_ty.toIntern())) { - .simple_type => unreachable, // filtered out above - .error_set_type, .inferred_error_set_type => return .from_anyerror, - else => unreachable, + const dest_set: InternPool.Key.ErrorSetType = err_set: switch (dest_ty.toIntern()) { + .anyerror_type => return .ok, + .adhoc_inferred_error_set_type => { + // We are trying to coerce an error set to the current function's + // inferred error set. + const dst_ies = sema.fn_ret_ty_ies.?; + try dst_ies.addErrorSet(src_ty, ip, sema.arena); + return .ok; }, - - else => switch (ip.indexToKey(src_ty.toIntern())) { - .inferred_error_set_type => { - const resolved_src_ty = try sema.resolveInferredErrorSet(block, src_src, src_ty.toIntern()); - // src anyerror status might have changed after the resolution. - if (resolved_src_ty == .anyerror_type) { - // dest_ty.isAnyError(zcu) == true is already checked for at this point. - return .from_anyerror; - } - - for (ip.indexToKey(resolved_src_ty).error_set_type.names.get(ip)) |key| { - if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), key)) { - try missing_error_buf.append(key); + else => |err_set_ty| switch (ip.indexToKey(err_set_ty)) { + .inferred_error_set_type => |func_index| { + if (sema.fn_ret_ty_ies) |dst_ies| { + if (dst_ies.func == func_index) { + // We are trying to coerce an error set to the current function's + // inferred error set. + try dst_ies.addErrorSet(src_ty, ip, sema.arena); + return .ok; } } - - if (missing_error_buf.items.len != 0) { - return InMemoryCoercionResult{ - .missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items), - }; - } - - return .ok; + try sema.ensureFuncIesResolved(block, dest_src, func_index); + continue :err_set ip.funcIesResolvedUnordered(func_index); }, - .error_set_type => |error_set_type| { - for (error_set_type.names.get(ip)) |name| { - if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), name)) { - try missing_error_buf.append(name); - } - } - - if (missing_error_buf.items.len != 0) { - return InMemoryCoercionResult{ - .missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items), - }; - } - - return .ok; + .error_set_type => |err_set| err_set, + else => unreachable, + }, + }; + + const src_names: InternPool.NullTerminatedString.Slice = err_set: switch (src_ty.toIntern()) { + .anyerror_type => return .from_anyerror, + else => |err_set_ty| switch (ip.indexToKey(err_set_ty)) { + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src_src, func_index); + continue :err_set ip.funcIesResolvedUnordered(func_index); }, + .error_set_type => |err_set| err_set.names, else => unreachable, }, + }; + + var missing_error_buf: std.ArrayList(InternPool.NullTerminatedString) = .empty; + defer missing_error_buf.deinit(gpa); + + for (src_names.get(ip)) |name| { + if (dest_set.nameIndex(ip, name) == null) { + try missing_error_buf.append(gpa, name); + } } + + if (missing_error_buf.items.len != 0) { + return .{ .missing_error = try sema.arena.dupe( + InternPool.NullTerminatedString, + missing_error_buf.items, + ) }; + } + + return .ok; } fn coerceInMemoryAllowedFns( @@ -30357,76 +30354,34 @@ fn resolveIsNonErrFromType( // exception if the error union error set is known to be empty, // we allow the comparison but always make it comptime-known. - const set_ty = ip.errorUnionSet(operand_ty.toIntern()); - switch (set_ty) { - .anyerror_type => {}, - .adhoc_inferred_error_set_type => if (sema.fn_ret_ty_ies) |ies| blk: { - // If the error set is empty, we must return a comptime true or false. - // However we want to avoid unnecessarily resolving an inferred error set - // in case it is already non-empty. - switch (ies.resolved) { - .anyerror_type => break :blk, - .none => {}, - else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk, - } - - if (ies.errors.count() != 0) return null; - switch (ies.resolved) { - .anyerror_type => return null, - .none => {}, - else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) { - 0 => return .true, - else => return null, - }, - } - // We do not have a comptime answer because this inferred error - // set is not resolved, and an instruction later in this function - // body may or may not cause an error to be added to this set. - return null; + return err_set: switch (ip.errorUnionSet(operand_ty.toIntern())) { + .anyerror_type => null, + .adhoc_inferred_error_set_type => { + // 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; }, - else => switch (ip.indexToKey(set_ty)) { - .error_set_type => |error_set_type| { - if (error_set_type.names.len == 0) return .true; + else => |set_ty| switch (ip.indexToKey(set_ty)) { + .error_set_type => |error_set_type| switch (error_set_type.names.len) { + 0 => .true, + else => null, }, - .inferred_error_set_type => |func_index| blk: { - // If the error set is empty, we must return a comptime true or false. - // However we want to avoid unnecessarily resolving an inferred error set - // in case it is already non-empty. - try zcu.maybeUnresolveIes(func_index); - switch (ip.funcIesResolvedUnordered(func_index)) { - .anyerror_type => break :blk, - .none => {}, - else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk, - } + .inferred_error_set_type => |func_index| { if (sema.fn_ret_ty_ies) |ies| { if (ies.func == func_index) { - // Try to avoid resolving inferred error set if possible. - if (ies.errors.count() != 0) return null; - switch (ies.resolved) { - .anyerror_type => return null, - .none => {}, - else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) { - 0 => return .true, - else => return null, - }, - } - // We do not have a comptime answer because this inferred error - // set is not resolved, and an instruction later in this function - // body may or may not cause an error to be added to this set. + // 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. return null; } } - const resolved_ty = try sema.resolveInferredErrorSet(block, src, set_ty); - if (resolved_ty == .anyerror_type) - break :blk; - if (ip.indexToKey(resolved_ty).error_set_type.names.len == 0) - return .true; + try sema.ensureFuncIesResolved(block, src, func_index); + continue :err_set ip.funcIesResolvedUnordered(func_index); }, else => unreachable, }, - } - - return null; + }; } fn analyzeIsNonErr( @@ -31384,58 +31339,16 @@ fn wrapErrorUnionSet( const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - const inst_ty = sema.typeOf(inst); const dest_err_set_ty = dest_ty.errorUnionSet(zcu); - if (sema.resolveValue(inst)) |val| { - const expected_name = zcu.intern_pool.indexToKey(val.toIntern()).err.name; - switch (dest_err_set_ty.toIntern()) { - .anyerror_type => {}, - .adhoc_inferred_error_set_type => ok: { - const ies = sema.fn_ret_ty_ies.?; - switch (ies.resolved) { - .anyerror_type => break :ok, - .none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) { - break :ok; - }, - else => |i| if (ip.indexToKey(i).error_set_type.nameIndex(ip, expected_name) != null) { - break :ok; - }, - } - return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty); - }, - else => switch (ip.indexToKey(dest_err_set_ty.toIntern())) { - .error_set_type => |error_set_type| ok: { - if (error_set_type.nameIndex(ip, expected_name) != null) break :ok; - return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty); - }, - .inferred_error_set_type => |func_index| ok: { - // We carefully do this in an order that avoids unnecessarily - // resolving the destination error set type. - try zcu.maybeUnresolveIes(func_index); - switch (ip.funcIesResolvedUnordered(func_index)) { - .anyerror_type => break :ok, - .none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) { - break :ok; - }, - else => |i| if (ip.indexToKey(i).error_set_type.nameIndex(ip, expected_name) != null) { - break :ok; - }, - } - - return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty); - }, - else => unreachable, - }, - } - return Air.internedToRef((try pt.intern(.{ .error_union = .{ + const coerced = try sema.coerceExtra(block, dest_err_set_ty, inst, inst_src, .{ .report_err = false }); + if (try sema.resolveDefinedValue(block, inst_src, coerced)) |error_val| { + return .fromIntern(try pt.intern(.{ .error_union = .{ .ty = dest_ty.toIntern(), - .val = .{ .err_name = expected_name }, - } }))); + .val = .{ .err_name = ip.indexToKey(error_val.toIntern()).err.name }, + } })); + } else { + return block.addTyOp(.wrap_errunion_err, dest_ty, coerced); } - - try sema.requireRuntimeBlock(block, inst_src, null); - const coerced = try sema.coerce(block, dest_err_set_ty, inst, inst_src); - return block.addTyOp(.wrap_errunion_err, dest_ty, coerced); } fn unionToTag( @@ -32969,18 +32882,6 @@ fn typeIsArrayLike(sema: *Sema, ty: Type) ?ArrayLike { }; } -pub fn resolveIes(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!void { - const pt = sema.pt; - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - - if (sema.fn_ret_ty_ies) |ies| { - try sema.resolveInferredErrorSetPtr(block, src, ies); - assert(ies.resolved != .none); - ip.funcIesResolved(sema.func_index).* = ies.resolved; - } -} - fn checkIndexable(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void { const pt = sema.pt; if (!ty.isIndexable(pt.zcu)) { @@ -33017,63 +32918,31 @@ fn checkMemOperand(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void return sema.failWithOwnedErrorMsg(block, msg); } -/// Returns a normal error set corresponding to the fully populated inferred -/// error set. -fn resolveInferredErrorSet( +/// Resolves the inferred error set of the given function, so that the corresponding concrete error +/// set is available by calling `InternPool.funcIesResolvedUnordered` on `func_index`. +/// +/// Asserts that `func_index` is a function. Also asserts that it is not a coerced function, because +/// coerced functions do not own inferred error sets. +fn ensureFuncIesResolved( sema: *Sema, block: *Block, src: LazySrcLoc, - ies_index: InternPool.Index, -) CompileError!InternPool.Index { + func_index: InternPool.Index, +) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - const func_index = ip.iesFuncIndex(ies_index); - const func = zcu.funcInfo(func_index); + + assert(ip.unwrapCoercedFunc(func_index) == func_index); try sema.declareDependency(.{ .func_ies = func_index }); - - // MLUGG TODO: this feels kinda bad now... instead check for outdated whenver we grab this? - try zcu.maybeUnresolveIes(func_index); - const resolved_ty = func.resolvedErrorSetUnordered(ip); - if (resolved_ty != .none) return resolved_ty; + try sema.addReferenceEntry(block, src, .wrap(.{ .func = func_index })); if (zcu.analysis_in_progress.contains(.wrap(.{ .func = func_index }))) { return sema.fail(block, src, "unable to resolve inferred error set", .{}); } - // In order to ensure that all dependencies are properly added to the set, - // we need to ensure the function body is analyzed of the inferred error - // set. However, in the case of comptime/inline function calls with - // inferred error sets, each call gets an adhoc InferredErrorSet object, which - // has no corresponding function body. - const ies_func_info = zcu.typeToFunc(.fromInterned(func.ty)).?; - // if ies declared by a inline function with generic return type, the return_type should be generic_poison, - // because inline function does not create a new declaration, and the ies has been filled with analyzeCall, - // so here we can simply skip this case. - if (ies_func_info.return_type == .generic_poison_type) { - assert(ies_func_info.cc == .@"inline"); - } else if (ip.errorUnionSet(ies_func_info.return_type) == ies_index) { - if (!Type.fromInterned(func.ty).fnHasRuntimeBits(zcu)) { - return sema.failWithOwnedErrorMsg(block, msg: { - const msg = try sema.errMsg(src, "unable to resolve inferred error set of generic function", .{}); - errdefer msg.destroy(sema.gpa); - try sema.errNote(zcu.navSrcLoc(func.owner_nav), msg, "generic function declared here", .{}); - break :msg msg; - }); - } - // In this case we are dealing with the actual InferredErrorSet object that - // corresponds to the function, not one created to track an inline/comptime call. - const orig_func_index = ip.unwrapCoercedFunc(func_index); - try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_func_index })); - try pt.ensureFuncBodyUpToDate(orig_func_index); - } - - // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody` - // which calls `resolveInferredErrorSetPtr`. - const final_resolved_ty = func.resolvedErrorSetUnordered(ip); - assert(final_resolved_ty != .none); - return final_resolved_ty; + try pt.ensureFuncBodyUpToDate(func_index); } pub fn resolveInferredErrorSetPtr( @@ -33091,7 +32960,9 @@ pub fn resolveInferredErrorSetPtr( for (ies.inferred_error_sets.keys()) |other_ies_index| { if (ies_index == other_ies_index) continue; - switch (try sema.resolveInferredErrorSet(block, src, other_ies_index)) { + const other_func_index = ip.iesFuncIndex(other_ies_index); + try sema.ensureFuncIesResolved(block, src, other_func_index); + switch (ip.funcIesResolvedUnordered(other_func_index)) { .anyerror_type => { ies.resolved = .anyerror_type; return; @@ -33164,7 +33035,10 @@ fn resolveInferredErrorSetTy( if (ty == .anyerror_type) return ty; switch (ip.indexToKey(ty)) { .error_set_type => return ty, - .inferred_error_set_type => return sema.resolveInferredErrorSet(block, src, ty), + .inferred_error_set_type => |func_index| { + try sema.ensureFuncIesResolved(block, src, func_index); + return ip.funcIesResolvedUnordered(func_index); + }, else => unreachable, } } diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index f346d217ae2b361af26498ba5a0f86651c1d6a08..f18cf9aaaeae1557d1a69799516d0f7687096dd0 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -132,7 +132,7 @@ pub fn resolveStructLayout(sema: *Sema, struct_ty: Type) CompileError!void { .src_base_inst = struct_obj.zir_index, .type_name_ctx = struct_obj.name, }; - defer assert(block.instructions.items.len == 0); + defer block.instructions.deinit(gpa); // There may be old field names in here from a previous update. struct_obj.field_name_map.get(ip).clearRetainingCapacity(); @@ -452,6 +452,8 @@ fn resolvePackedStructLayout( pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void { const pt = sema.pt; const zcu = pt.zcu; + const comp = zcu.comp; + const gpa = comp.gpa; const ip = &zcu.intern_pool; assert(sema.owner.unwrap().struct_defaults == struct_ty.toIntern()); @@ -490,7 +492,7 @@ pub fn resolveStructDefaults(sema: *Sema, struct_ty: Type) CompileError!void { .src_base_inst = struct_obj.zir_index, .type_name_ctx = struct_obj.name, }; - defer assert(block.instructions.items.len == 0); + defer block.instructions.deinit(gpa); return resolveStructDefaultsInner(sema, &block, &struct_obj); } @@ -565,7 +567,7 @@ pub fn resolveUnionLayout(sema: *Sema, union_ty: Type) CompileError!void { .src_base_inst = union_obj.zir_index, .type_name_ctx = union_obj.name, }; - defer assert(block.instructions.items.len == 0); + defer block.instructions.deinit(gpa); // MLUGG TODO: this is fucking ugly bro const explicit_enum_tag_ty: ?Type = if (union_obj.is_reified) ty: { @@ -1011,7 +1013,7 @@ pub fn resolveEnumLayout(sema: *Sema, enum_ty: Type) CompileError!void { .src_base_inst = tracked_inst, .type_name_ctx = enum_obj.name, }; - defer assert(block.instructions.items.len == 0); + defer block.instructions.deinit(gpa); // There may be old field names in the map from a previous update. enum_obj.field_name_map.get(ip).clearRetainingCapacity(); diff --git a/src/Type.zig b/src/Type.zig index 111f6347ed2d0f72cd70db20a59dfc806fe140e8..ca94c09bf04fc00503dc414541a0861194cf4256 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -1472,14 +1472,15 @@ pub fn isError(ty: Type, zcu: *const Zcu) bool { /// Returns whether ty, which must be an error set, includes an error `name`. /// Might return a false negative if `ty` is an inferred error set and not fully /// resolved yet. -pub fn errorSetHasFieldIp( - ip: *const InternPool, - ty: InternPool.Index, +pub fn errorSetHasField( + ty: Type, name: InternPool.NullTerminatedString, + zcu: *const Zcu, ) bool { - return switch (ty) { + const ip = &zcu.intern_pool; + return switch (ty.toIntern()) { .anyerror_type => true, - else => switch (ip.indexToKey(ty)) { + else => switch (ip.indexToKey(ty.toIntern())) { .error_set_type => |error_set_type| error_set_type.nameIndex(ip, name) != null, .inferred_error_set_type => |i| switch (ip.funcIesResolvedUnordered(i)) { .anyerror_type => true, diff --git a/src/Value.zig b/src/Value.zig index ca9ef9604627beb12c9014b9372f42eb1aac538a..de7aacd1e1f2c84737acb243aa1565ac5fc0a94b 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -641,9 +641,9 @@ pub fn readFromPackedMemory( .optional => { assert(ty.isPtrLikeOptional(zcu)); const addr = (try readFromPackedMemory(Type.usize, pt, buffer, bit_offset, arena)).toUnsignedInt(zcu); - return Value.fromInterned(try pt.intern(.{ .opt = .{ + return .fromInterned(try pt.intern(.{ .opt = .{ .ty = ty.toIntern(), - .val = (try pt.ptrIntValue(ty.childType(zcu), addr)).toIntern(), + .val = if (addr == 0) .none else (try pt.ptrIntValue(ty.childType(zcu), addr)).toIntern(), } })); }, else => @panic("TODO implement readFromPackedMemory for more types"), diff --git a/src/Zcu.zig b/src/Zcu.zig index 5aef6a11d17d13d43176c782d53b3de4287bc2af..3b17bc1c0981d1e19f57db43412f4b309484faf4 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4059,6 +4059,7 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoArrayHashMapUnmanaged(AnalUnit, ?R implicit_tag: { const loaded_union = zcu.typeToUnion(.fromInterned(ty)) orelse break :implicit_tag; const tag_ty = loaded_union.enum_tag_type; + if (tag_ty == .none) break :implicit_tag; if (ip.indexToKey(tag_ty).enum_type != .generated_union_tag) break :implicit_tag; const gop = try types.getOrPut(gpa, tag_ty); if (gop.found_existing) break :implicit_tag; @@ -4383,32 +4384,6 @@ fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void } } -/// Given the `InternPool.Index` of a function, set its resolved IES to `.none` if it -/// may be outdated. `Sema` should do this before ever loading a resolved IES. -pub fn maybeUnresolveIes(zcu: *Zcu, func_index: InternPool.Index) !void { - const unit = AnalUnit.wrap(.{ .func = func_index }); - if (zcu.outdated.contains(unit) or zcu.potentially_outdated.contains(unit)) { - // We're consulting the resolved IES now, but the function is outdated, so its - // IES may have changed. We have to assume the IES is outdated and set the resolved - // set back to `.none`. - // - // This will cause `PerThread.analyzeFnBody` to mark the IES as outdated when it's - // eventually hit. - // - // Since the IES needs to be resolved, the function body will now definitely need - // re-analysis (even if the IES turns out to be the same!), so mark it as - // definitely-outdated if it's only PO. - if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { - const gpa = zcu.gpa; - try zcu.outdated.putNoClobber(gpa, unit, kv.value); - if (kv.value == 0) { - try zcu.outdated_ready.put(gpa, unit, {}); - } - } - zcu.intern_pool.funcSetIesResolved(zcu.comp.io, func_index, .none); - } -} - pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enum) { ok, bad_arch: []const std.Target.Cpu.Arch, // value is allowed archs for cc -- 2.54.0