From 139bd01ac173c7532d07599d017cdaef718b0fdf Mon Sep 17 00:00:00 2001 From: mlugg Date: Tue, 16 May 2023 03:51:35 +0100 Subject: [PATCH] Sema: rewrite peer type resolution The existing logic for peer type resolution was quite convoluted and buggy. This rewrite makes it much more resilient, readable, and extensible. The algorithm works by first iterating over the types to select a "strategy", then applying that strategy, possibly applying peer resolution recursively. The new semantics around PTR for comptime-known fixed-width integers did require some small changes to parts of the compiler, std, and compiler_rt. Regarding the MachO changes, I spoke to @kubkon about how best to do them, and this is what he suggested. Several new tests have been added to cover cases which the old logic did not correctly handle. Resolves: #9917 Resolves: #15644 Resolves: #15709 --- lib/compiler_rt/exp2.zig | 2 +- lib/std/Build/Step/CheckObject.zig | 2 +- lib/std/crypto/kyber_d00.zig | 2 +- lib/std/fmt/parse_float/parse.zig | 2 +- lib/std/macho.zig | 4 + src/Sema.zig | 1561 +++++++++++------ src/link/MachO.zig | 9 +- src/link/MachO/dyld_info/bind.zig | 6 +- src/link/MachO/zld.zig | 8 +- src/type.zig | 2 +- test/behavior/cast.zig | 153 ++ ...nal_to_non-optional_with_invalid_types.zig | 5 +- 12 files changed, 1213 insertions(+), 543 deletions(-) diff --git a/lib/compiler_rt/exp2.zig b/lib/compiler_rt/exp2.zig index 188236752244..cdc9e3a5a0a9 100644 --- a/lib/compiler_rt/exp2.zig +++ b/lib/compiler_rt/exp2.zig @@ -88,7 +88,7 @@ pub fn exp2f(x: f32) callconv(.C) f32 { } pub fn exp2(x: f64) callconv(.C) f64 { - const tblsiz: u32 = @intCast(u32, exp2dt.len / 2); + const tblsiz: u16 = exp2dt.len / 2; const redux: f64 = 0x1.8p52 / @intToFloat(f64, tblsiz); const P1: f64 = 0x1.62e42fefa39efp-1; const P2: f64 = 0x1.ebfbdff82c575p-3; diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index c77dc3de3664..ea22a060947b 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -503,7 +503,7 @@ const MachODumper = struct { } try writer.print(" {s}\n", .{sym_name}); } else if (sym.undf()) { - const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const ordinal = sym.getDylibOrdinal(); const import_name = blk: { if (ordinal <= 0) { if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) diff --git a/lib/std/crypto/kyber_d00.zig b/lib/std/crypto/kyber_d00.zig index dca4ab7ea760..e200dcaf06cc 100644 --- a/lib/std/crypto/kyber_d00.zig +++ b/lib/std/crypto/kyber_d00.zig @@ -1021,7 +1021,7 @@ const Poly = struct { // = ⌊((x << d) + q/2) / q⌋ mod⁺ 2ᵈ // = DIV((x << d) + q/2, q) & ((1< switch (dest_ty.tag()) { - .error_set_inferred => unreachable, // Caught by dest_ty.isAnyError() above. + .error_set_inferred => return .from_anyerror, // Caught by dest_ty.isAnyError() above. .error_set_single, .error_set_merged, .error_set => return .from_anyerror, .anyerror => unreachable, // Filtered out above. else => unreachable, }, else => unreachable, } - - unreachable; } fn coerceInMemoryAllowedFns( @@ -30175,6 +30173,225 @@ fn unionToTag( return block.addTyOp(.get_union_tag, enum_ty, un); } +const PeerResolveStrategy = enum { + /// The type is not known. + /// If refined no further, this is equivalent to `exact`. + unknown, + /// The type may be an error set or error union. + /// If refined no further, it is an error set. + error_set, + /// The type must be some error union. + error_union, + /// The type may be @TypeOf(null), an optional or a C pointer. + /// If refined no further, it is @TypeOf(null). + nullable, + /// The type must be some optional or a C pointer. + /// If refined no further, it is an optional. + optional, + /// The type must be either an array or a vector. + /// If refined no further, it is an array. + array, + /// The type must be a vector. + vector, + /// The type must be a C pointer. + c_ptr, + /// The type must be a pointer (C or not). + /// If refined no further, it is a non-C pointer. + ptr, + /// The type must be a function or a pointer to a function. + /// If refined no further, it is a function. + func, + /// The type must be an enum literal, or some specific enum or union. Which one is decided + /// afterwards based on the types in question. + enum_or_union, + /// The type must be some integer or float type. + /// If refined no further, it is `comptime_int`. + comptime_int, + /// The type must be some float type. + /// If refined no further, it is `comptime_float`. + comptime_float, + /// The type must be some float or fixed-width integer type. + /// If refined no further, it is some fixed-width integer type. + fixed_int, + /// The type must be some fixed-width float type. + fixed_float, + /// The peers must all be of the same type. + exact, + + const Reason = struct { + peers: std.DynamicBitSet, + fn reset(r: *Reason) void { + r.peers.setRangeValue(.{ .start = 0, .end = r.peers.capacity() }, false); + } + }; + + fn name(s: PeerResolveStrategy) []const u8 { + return switch (s) { + .unknown, .exact => "exact", + .error_set => "error set", + .error_union => "error union", + .nullable => "null", + .optional => "optional", + .array => "array", + .vector => "vector", + .c_ptr => "C pointer", + .ptr => "pointer", + .func => "function", + .enum_or_union => "enum or union", + .comptime_int => "comptime_int", + .comptime_float => "comptime_float", + .fixed_int => "fixed-width int", + .fixed_float => "fixed-width float", + }; + } + + /// Given two strategies, find a strategy that satisfies both, if one exists. If no such + /// strategy exists, any strategy may be returned; an error will be emitted when the caller + /// attempts to use the strategy to resolve the type. + /// Strategy `a` comes from the peers set in `reason`, while strategy `b` comes from the peer at + /// index `b_peer_idx`. `reason` will be updated to reflect the reason for the new strategy. + fn merge(a: PeerResolveStrategy, b: PeerResolveStrategy, reason: *Reason, b_peer_idx: usize) PeerResolveStrategy { + // Our merging should be order-independent. Thus, even though the union order is arbitrary, + // by sorting the tags and switching first on the smaller, we have half as many cases to + // worry about (since we avoid the duplicates). + const s0_is_a = @enumToInt(a) <= @enumToInt(b); + const s0 = if (s0_is_a) a else b; + const s1 = if (s0_is_a) b else a; + + const ReasonMethod = enum { + all_s0, + all_s1, + either, + both, + }; + + const res: struct { ReasonMethod, PeerResolveStrategy } = switch (s0) { + .unknown => .{ .all_s1, s1 }, + .error_set => switch (s1) { + .error_set => .{ .either, .error_set }, + else => .{ .both, .error_union }, + }, + .error_union => switch (s1) { + .error_union => .{ .either, .error_union }, + else => .{ .all_s0, .error_union }, + }, + .nullable => switch (s1) { + .nullable => .{ .either, .nullable }, + .c_ptr => .{ .all_s1, .c_ptr }, + else => .{ .both, .optional }, + }, + .optional => switch (s1) { + .optional => .{ .either, .optional }, + .c_ptr => .{ .all_s1, .c_ptr }, + else => .{ .all_s0, .optional }, + }, + .array => switch (s1) { + .array => .{ .either, .array }, + .vector => .{ .all_s1, .vector }, + else => .{ .all_s0, .array }, + }, + .vector => switch (s1) { + .vector => .{ .either, .vector }, + else => .{ .all_s0, .vector }, + }, + .c_ptr => switch (s1) { + .c_ptr => .{ .either, .c_ptr }, + else => .{ .all_s0, .c_ptr }, + }, + .ptr => switch (s1) { + .ptr => .{ .either, .ptr }, + else => .{ .all_s0, .ptr }, + }, + .func => switch (s1) { + .func => .{ .either, .func }, + else => .{ .all_s1, s1 }, // doesn't override anything later + }, + .enum_or_union => switch (s1) { + .enum_or_union => .{ .either, .enum_or_union }, + else => .{ .all_s0, .enum_or_union }, + }, + .comptime_int => switch (s1) { + .comptime_int => .{ .either, .comptime_int }, + else => .{ .all_s1, s1 }, // doesn't override anything later + }, + .comptime_float => switch (s1) { + .comptime_float => .{ .either, .comptime_float }, + else => .{ .all_s1, s1 }, // doesn't override anything later + }, + .fixed_int => switch (s1) { + .fixed_int => .{ .either, .fixed_int }, + else => .{ .all_s1, s1 }, // doesn't override anything later + }, + .fixed_float => switch (s1) { + .fixed_float => .{ .either, .fixed_float }, + else => .{ .all_s1, s1 }, // doesn't override anything later + }, + .exact => .{ .all_s0, .exact }, + }; + + switch (res[0]) { + .all_s0 => { + if (!s0_is_a) { + reason.reset(); + reason.peers.set(b_peer_idx); + } + }, + .all_s1 => { + if (s0_is_a) { + reason.reset(); + reason.peers.set(b_peer_idx); + } + }, + .either => { + // Prefer b, since it's a single peer + reason.reset(); + reason.peers.set(b_peer_idx); + }, + .both => { + reason.peers.set(b_peer_idx); + }, + } + + return res[1]; + } + + fn select(ty: Type) PeerResolveStrategy { + return switch (ty.zigTypeTag()) { + .Type, .Void, .Bool, .Opaque, .Frame, .AnyFrame => .exact, + .NoReturn, .Undefined => .unknown, + .Null => .nullable, + .ComptimeInt => .comptime_int, + .Int => .fixed_int, + .ComptimeFloat => .comptime_float, + .Float => .fixed_float, + .Pointer => if (ty.ptrInfo().data.size == .C) .c_ptr else .ptr, + .Array => .array, + .Vector => .vector, + .Optional => .optional, + .ErrorSet => .error_set, + .ErrorUnion => .error_union, + .EnumLiteral, .Enum, .Union => .enum_or_union, + .Struct => .exact, // TODO: make tuples better! + .Fn => .func, + }; + } +}; + +const PeerResolveResult = union(enum) { + /// The peer type resolution was successful, and resulted in the given type. + success: Type, + /// The chosen strategy was incompatible with the given peer. + bad_strat: struct { + strat: PeerResolveStrategy, + peer_idx: usize, + }, + /// There was some conflict between two specific peers. + conflict: struct { + peer_idx_a: usize, + peer_idx_b: usize, + }, +}; + fn resolvePeerTypes( sema: *Sema, block: *Block, @@ -30188,590 +30405,892 @@ fn resolvePeerTypes( else => {}, } - const target = sema.mod.getTarget(); - - var chosen = instructions[0]; - // If this is non-null then it does the following thing, depending on the chosen zigTypeTag(). - // * ErrorSet: this is an override - // * ErrorUnion: this is an override of the error set only - // * other: at the end we make an ErrorUnion with the other thing and this - var err_set_ty: ?Type = null; - var any_are_null = false; - var seen_const = false; - var convert_to_slice = false; - var chosen_i: usize = 0; - for (instructions[1..], 0..) |candidate, candidate_i| { - const candidate_ty = sema.typeOf(candidate); - const chosen_ty = sema.typeOf(chosen); - - const candidate_ty_tag = try candidate_ty.zigTypeTagOrPoison(); - const chosen_ty_tag = try chosen_ty.zigTypeTagOrPoison(); - - // If the candidate can coerce into our chosen type, we're done. - // If the chosen type can coerce into the candidate, use that. - if ((try sema.coerceInMemoryAllowed(block, chosen_ty, candidate_ty, false, target, src, src)) == .ok) { - continue; - } - if ((try sema.coerceInMemoryAllowed(block, candidate_ty, chosen_ty, false, target, src, src)) == .ok) { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } - - switch (candidate_ty_tag) { - .NoReturn, .Undefined => continue, + var peer_tys = try sema.arena.alloc(?Type, instructions.len); + var peer_vals = try sema.arena.alloc(?Value, instructions.len); - .Null => { - any_are_null = true; - continue; - }, + for (instructions, peer_tys, peer_vals) |inst, *ty, *val| { + ty.* = sema.typeOf(inst); + val.* = try sema.resolveMaybeUndefVal(inst); + } - .Int => switch (chosen_ty_tag) { - .ComptimeInt => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - .Int => { - const chosen_info = chosen_ty.intInfo(target); - const candidate_info = candidate_ty.intInfo(target); + var strat_reason: PeerResolveStrategy.Reason = .{ + .peers = try std.DynamicBitSet.initEmpty(sema.arena, instructions.len), + }; - if (chosen_info.bits < candidate_info.bits) { - chosen = candidate; - chosen_i = candidate_i + 1; - } - continue; + var conflict_idx_a: usize = undefined; + var conflict_idx_b: usize = undefined; + + switch (try sema.resolvePeerTypesInner(block, src, peer_tys, peer_vals, &strat_reason)) { + .success => |ty| return ty, + .bad_strat => |bad_strat| simple_err: { + switch (strat_reason.peers.count()) { + 0 => { + // Something weird happened - just mark every other peer as contributing to the strategy + strat_reason.peers.toggleAll(); + strat_reason.peers.unset(bad_strat.peer_idx); }, - .Pointer => if (chosen_ty.ptrSize() == .C) continue, - else => {}, - }, - .ComptimeInt => switch (chosen_ty_tag) { - .Int, .Float, .ComptimeFloat => continue, - .Pointer => if (chosen_ty.ptrSize() == .C) continue, - else => {}, - }, - .Float => switch (chosen_ty_tag) { - .Float => { - if (chosen_ty.floatBits(target) < candidate_ty.floatBits(target)) { - chosen = candidate; - chosen_i = candidate_i + 1; - } - continue; - }, - .ComptimeFloat, .ComptimeInt => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; + 1 => { + // We can write this error more simply as a general type conflict + conflict_idx_a = strat_reason.peers.findFirstSet().?; + conflict_idx_b = bad_strat.peer_idx; + break :simple_err; }, else => {}, - }, - .ComptimeFloat => switch (chosen_ty_tag) { - .Float => continue, - .ComptimeInt => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - else => {}, - }, - .Enum => switch (chosen_ty_tag) { - .EnumLiteral => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - .Union => continue, - else => {}, - }, - .EnumLiteral => switch (chosen_ty_tag) { - .Enum, .Union => continue, - else => {}, - }, - .Union => switch (chosen_ty_tag) { - .Enum, .EnumLiteral => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, + } + + const msg = msg: { + const msg = try sema.errMsg(block, src, "type resolution strategy failed", .{}); + errdefer msg.destroy(sema.gpa); + + const peer_ty = sema.typeOf(instructions[bad_strat.peer_idx]); + const peer_src = candidate_srcs.resolve( + sema.gpa, + sema.mod.declPtr(block.src_decl), + bad_strat.peer_idx, + ) orelse src; + try sema.errNote(block, peer_src, msg, "strategy '{s}' failed for type '{}' here", .{ bad_strat.strat.name(), peer_ty.fmt(sema.mod) }); + + try sema.errNote(block, src, msg, "strategy chosen using {} peers", .{strat_reason.peers.count()}); + var it = strat_reason.peers.iterator(.{}); + while (it.next()) |strat_peer_idx| { + const strat_peer_ty = sema.typeOf(instructions[strat_peer_idx]); + const strat_peer_src = candidate_srcs.resolve( + sema.gpa, + sema.mod.declPtr(block.src_decl), + strat_peer_idx, + ) orelse src; + try sema.errNote(block, strat_peer_src, msg, "peer of type '{}' here", .{strat_peer_ty.fmt(sema.mod)}); + } + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + }, + .conflict => |conflict| { + conflict_idx_a = conflict.peer_idx_a; + conflict_idx_b = conflict.peer_idx_b; + }, + } + + if (conflict_idx_b < conflict_idx_a) { + // b comes first in source, so it's better if it comes first in the error + std.mem.swap(usize, &conflict_idx_a, &conflict_idx_b); + } + + const ty_a = sema.typeOf(instructions[conflict_idx_a]); + const ty_b = sema.typeOf(instructions[conflict_idx_b]); + const src_a = candidate_srcs.resolve( + sema.gpa, + sema.mod.declPtr(block.src_decl), + conflict_idx_a, + ); + const src_b = candidate_srcs.resolve( + sema.gpa, + sema.mod.declPtr(block.src_decl), + conflict_idx_b, + ); + const msg = msg: { + const msg = try sema.errMsg(block, src, "incompatible types: '{}' and '{}'", .{ ty_a.fmt(sema.mod), ty_b.fmt(sema.mod) }); + errdefer msg.destroy(sema.gpa); + if (src_a) |src_loc| try sema.errNote(block, src_loc, msg, "type '{}' here", .{ty_a.fmt(sema.mod)}); + if (src_b) |src_loc| try sema.errNote(block, src_loc, msg, "type '{}' here", .{ty_b.fmt(sema.mod)}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); +} + +fn resolvePeerTypesInner( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + peer_tys: []?Type, + peer_vals: []?Value, + strat_reason: *PeerResolveStrategy.Reason, +) !PeerResolveResult { + strat_reason.reset(); + + var s: PeerResolveStrategy = .unknown; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + s = s.merge(PeerResolveStrategy.select(ty), strat_reason, i); + } + + if (s == .unknown) { + // The whole thing was noreturn or undefined - try to do an exact match + s = .exact; + } else { + // There was something other than noreturn and undefined, so we can ignore those peers + for (peer_tys) |*ty_ptr| { + const ty = ty_ptr.* orelse continue; + switch (ty.zigTypeTag()) { + .NoReturn, .Undefined => ty_ptr.* = null, else => {}, - }, - .ErrorSet => switch (chosen_ty_tag) { - .ErrorSet => { - // If chosen is superset of candidate, keep it. - // If candidate is superset of chosen, switch it. - // If neither is a superset, merge errors. - const chosen_set_ty = err_set_ty orelse chosen_ty; + } + } + } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { - continue; - } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { - err_set_ty = null; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } + const target = sema.mod.getTarget(); - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); - continue; - }, - .ErrorUnion => { - const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); + switch (s) { + .unknown => unreachable, - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { - continue; - } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { - err_set_ty = candidate_ty; - continue; - } + .error_set => { + var final_set: ?Type = null; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + if (ty.zigTypeTag() != .ErrorSet) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + if (final_set) |cur_set| { + final_set = try sema.maybeMergeErrorSets(block, src, cur_set, ty); + } else { + final_set = ty; + } + } + return .{ .success = final_set.? }; + }, - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + .error_union => { + var final_set: ?Type = null; + for (peer_tys, peer_vals) |*ty_ptr, *val_ptr| { + const ty = ty_ptr.* orelse continue; + const set_ty = switch (ty.zigTypeTag()) { + .ErrorSet => blk: { + ty_ptr.* = null; // no payload to decide on + val_ptr.* = null; + break :blk ty; + }, + .ErrorUnion => blk: { + const set_ty = ty.errorUnionSet(); + ty_ptr.* = ty.errorUnionPayload(); + if (val_ptr.*) |eu_val| switch (eu_val.tag()) { + .eu_payload => val_ptr.* = eu_val.castTag(.eu_payload).?.data, + else => val_ptr.* = null, + }; + break :blk set_ty; + }, + else => continue, // whole type is the payload + }; + if (final_set) |cur_set| { + final_set = try sema.maybeMergeErrorSets(block, src, cur_set, set_ty); + } else { + final_set = set_ty; + } + } + assert(final_set != null); + const final_payload = switch (try sema.resolvePeerTypesInner( + block, + src, + peer_tys, + peer_vals, + strat_reason, + )) { + .success => |ty| ty, + else => |result| return result, + }; + return .{ .success = try Type.errorUnion(sema.arena, final_set.?, final_payload, sema.mod) }; + }, + + .nullable => { + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + if (!ty.eql(Type.null, sema.mod)) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + } + return .{ .success = Type.null }; + }, + + .optional => { + for (peer_tys, peer_vals) |*ty_ptr, *val_ptr| { + const ty = ty_ptr.* orelse continue; + switch (ty.zigTypeTag()) { + .Null => { + ty_ptr.* = null; + val_ptr.* = null; + }, + .Optional => { + ty_ptr.* = try ty.optionalChildAlloc(sema.arena); + if (val_ptr.*) |opt_val| val_ptr.* = if (!opt_val.isUndef()) opt_val.optionalValue() else null; + }, + else => {}, + } + } + const child_ty = switch (try sema.resolvePeerTypesInner( + block, + src, + peer_tys, + peer_vals, + strat_reason, + )) { + .success => |ty| ty, + else => |result| return result, + }; + return .{ .success = try Type.optional(sema.arena, child_ty) }; + }, + + .array => { + var seen_peer = false; + var first_idx: usize = undefined; + var len: u64 = undefined; + var elem_ty: Type = undefined; + var sentinel: ?Value = undefined; + for (peer_tys, 0..) |*ty_ptr, i| { + const ty = ty_ptr.* orelse continue; + if (!ty.isArrayOrVector()) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + if (!seen_peer) { + first_idx = i; + len = ty.arrayLen(); + elem_ty = ty.childType(); + sentinel = ty.sentinel(); + seen_peer = true; continue; - }, - else => { - if (err_set_ty) |chosen_set_ty| { - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { - continue; - } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { - err_set_ty = candidate_ty; + } + if (ty.arrayLen() != len) return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + if (!ty.childType().eql(elem_ty, sema.mod)) return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + if (sentinel) |expect_sent| { + if (ty.sentinel()) |peer_sent| { + if (!peer_sent.eql(expect_sent, elem_ty, sema.mod)) sentinel = null; + } else { + sentinel = null; + } + } + } + assert(seen_peer); + return .{ .success = try Type.array(sema.arena, len, sentinel, elem_ty, sema.mod) }; + }, + + .vector => { + var len: ?u64 = null; + var first_idx: usize = undefined; + for (peer_tys, peer_vals, 0..) |*ty_ptr, *val_ptr, i| { + const ty = ty_ptr.* orelse continue; + if (!ty.isArrayOrVector()) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + if (len) |expect_len| { + if (ty.arrayLen() != expect_len) return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + } else { + len = ty.arrayLen(); + first_idx = i; + } + ty_ptr.* = ty.childType(); + val_ptr.* = null; // multiple child vals, so we can't easily use them in PTR + } + const child_ty = switch (try sema.resolvePeerTypesInner( + block, + src, + peer_tys, + peer_vals, + strat_reason, + )) { + .success => |ty| ty, + else => |result| return result, + }; + return .{ .success = try Type.vector(sema.arena, len.?, child_ty) }; + }, + + .c_ptr => { + var opt_ptr_info: ?Type.Payload.Pointer.Data = null; + var first_idx: usize = undefined; + for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag()) { + .ComptimeInt => continue, // comptime-known integers can always coerce to C pointers + .Int => { + if (opt_val != null) { + // Always allow the coercion for comptime-known ints continue; + } else { + // Runtime-known, so check if the type is no bigger than a usize + const ptr_bits = target.cpu.arch.ptrBitWidth(); + const bits = ty.intInfo(target).bits; + if (bits <= ptr_bits) continue; } + }, + .Null => continue, + else => {}, + } - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); - continue; - } else { - err_set_ty = candidate_ty; - continue; - } - }, - }, - .ErrorUnion => switch (chosen_ty_tag) { - .ErrorSet => { - const chosen_set_ty = err_set_ty orelse chosen_ty; - const candidate_set_ty = candidate_ty.errorUnionSet(); + if (!ty.isPtrAtRuntime()) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { - err_set_ty = chosen_set_ty; - } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { - err_set_ty = null; - } else { - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); - } - chosen = candidate; - chosen_i = candidate_i + 1; + // Goes through optionals + const peer_info = ty.ptrInfo().data; + + var ptr_info = opt_ptr_info orelse { + opt_ptr_info = peer_info; + opt_ptr_info.?.size = .C; + first_idx = i; continue; - }, + }; - .ErrorUnion => { - const chosen_payload_ty = chosen_ty.errorUnionPayload(); - const candidate_payload_ty = candidate_ty.errorUnionPayload(); + // Try peer -> cur, then cur -> peer + ptr_info.pointee_type = (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, peer_info.pointee_type)) orelse { + return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + }; - const coerce_chosen = (try sema.coerceInMemoryAllowed(block, chosen_payload_ty, candidate_payload_ty, false, target, src, src)) == .ok; - const coerce_candidate = (try sema.coerceInMemoryAllowed(block, candidate_payload_ty, chosen_payload_ty, false, target, src, src)) == .ok; + if (ptr_info.sentinel != null and peer_info.sentinel != null) { + if (!ptr_info.sentinel.?.eql(peer_info.sentinel.?, ptr_info.pointee_type, sema.mod)) { + ptr_info.sentinel = null; + } + // TODO: once InternPool gets in, we may need to actually cast the sentinel + // according to the in-memory coercion! + } else { + ptr_info.sentinel = null; + } - if (coerce_chosen or coerce_candidate) { - // If we can coerce to the candidate, we switch to that - // type. This is the same logic as the bare (non-union) - // coercion check we do at the top of this func. - if (coerce_candidate) { - chosen = candidate; - chosen_i = candidate_i + 1; - } + // Note that the align can be always non-zero; Type.ptr will canonicalize it + ptr_info.@"align" = @min(ptr_info.alignment(target), peer_info.alignment(target)); + if (ptr_info.@"addrspace" != peer_info.@"addrspace") { + return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + } - const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); - const candidate_set_ty = candidate_ty.errorUnionSet(); + if (ptr_info.bit_offset != peer_info.bit_offset or + ptr_info.host_size != peer_info.host_size) + { + return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { - err_set_ty = chosen_set_ty; - } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { - err_set_ty = candidate_set_ty; - } else { - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); - } - continue; - } - }, + ptr_info.mutable = ptr_info.mutable and peer_info.mutable; + ptr_info.@"volatile" = ptr_info.@"volatile" or peer_info.@"volatile"; - else => { - if (err_set_ty) |chosen_set_ty| { - const candidate_set_ty = candidate_ty.errorUnionSet(); - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { - err_set_ty = chosen_set_ty; - } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { - err_set_ty = null; - } else { - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); - } - } - seen_const = seen_const or chosen_ty.isConstPtr(); - chosen = candidate; - chosen_i = candidate_i + 1; + opt_ptr_info = ptr_info; + } + return .{ .success = try Type.ptr(sema.arena, sema.mod, opt_ptr_info.?) }; + }, + + .ptr => { + // If we've resolved to a `[]T` but then see a `[*]T`, we can resolve to a `[*]T` only + // if there were no actual slices. + var seen_slice = false; + var opt_ptr_info: ?Type.Payload.Pointer.Data = null; + var first_idx: usize = undefined; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + const peer_info: Type.Payload.Pointer.Data = switch (ty.zigTypeTag()) { + .Pointer => ty.ptrInfo().data, + .Fn => .{ + .pointee_type = ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .global_constant), + }, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + }; + + switch (peer_info.size) { + .One, .Many => {}, + .Slice => seen_slice = true, + .C => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + + var ptr_info = opt_ptr_info orelse { + opt_ptr_info = peer_info; + first_idx = i; continue; - }, - }, - .Pointer => { - const cand_info = candidate_ty.ptrInfo().data; - switch (chosen_ty_tag) { - .Pointer => { - const chosen_info = chosen_ty.ptrInfo().data; + }; - seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; + // We want to return this in a lot of cases, so alias it here for convenience + const generic_err: PeerResolveResult = .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; - // *[N]T to [*]T - // *[N]T to []T - if ((cand_info.size == .Many or cand_info.size == .Slice) and - chosen_info.size == .One and - chosen_info.pointee_type.zigTypeTag() == .Array) - { - // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` - convert_to_slice = false; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } - if (cand_info.size == .One and - cand_info.pointee_type.zigTypeTag() == .Array and - (chosen_info.size == .Many or chosen_info.size == .Slice)) - { - // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` - convert_to_slice = false; - continue; - } + // Note that the align can be always non-zero; Type.ptr will canonicalize it + ptr_info.@"align" = @min(ptr_info.alignment(target), peer_info.alignment(target)); - // *[N]T and *[M]T - // Verify both are single-pointers to arrays. - // Keep the one whose element type can be coerced into. - if (chosen_info.size == .One and - cand_info.size == .One and - chosen_info.pointee_type.zigTypeTag() == .Array and - cand_info.pointee_type.zigTypeTag() == .Array) - { - const chosen_elem_ty = chosen_info.pointee_type.childType(); - const cand_elem_ty = cand_info.pointee_type.childType(); + if (ptr_info.@"addrspace" != peer_info.@"addrspace") { + return generic_err; + } - const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_elem_ty, cand_elem_ty, chosen_info.mutable, target, src, src); - if (chosen_ok) { - convert_to_slice = true; - continue; - } + if (ptr_info.bit_offset != peer_info.bit_offset or + ptr_info.host_size != peer_info.host_size) + { + return generic_err; + } - const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_elem_ty, chosen_elem_ty, cand_info.mutable, target, src, src); - if (cand_ok) { - convert_to_slice = true; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } + ptr_info.mutable = ptr_info.mutable and peer_info.mutable; + ptr_info.@"volatile" = ptr_info.@"volatile" or peer_info.@"volatile"; - // They're both bad. Report error. - // In the future we probably want to use the - // coerceInMemoryAllowed error reporting mechanism, - // however, for now we just fall through for the - // "incompatible types" error below. - } + const peer_sentinel: ?Value = switch (peer_info.size) { + .One => switch (peer_info.pointee_type.zigTypeTag()) { + .Array => peer_info.pointee_type.sentinel(), + else => null, + }, + .Many, .Slice => peer_info.sentinel, + .C => unreachable, + }; + + const cur_sentinel: ?Value = switch (ptr_info.size) { + .One => switch (ptr_info.pointee_type.zigTypeTag()) { + .Array => ptr_info.pointee_type.sentinel(), + else => null, + }, + .Many, .Slice => ptr_info.sentinel, + .C => unreachable, + }; - // [*c]T and any other pointer size - // Whichever element type can coerce to the other one, is - // the one we will keep. If they're both OK then we keep the - // C pointer since it matches both single and many pointers. - if (cand_info.size == .C or chosen_info.size == .C) { - const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_info.pointee_type, chosen_info.pointee_type, cand_info.mutable, target, src, src); - const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_info.pointee_type, cand_info.pointee_type, chosen_info.mutable, target, src, src); - - if (cand_ok) { - if (chosen_ok) { - if (chosen_info.size == .C) { - continue; - } else { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; + // This switch is just responsible for deciding the size and pointee (not including + // single-pointer array sentinel). + good: { + switch (peer_info.size) { + .One => switch (ptr_info.size) { + .One => { + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, peer_info.pointee_type)) |pointee| { + ptr_info.pointee_type = pointee; + break :good; + } + + if (ptr_info.pointee_type.zigTypeTag() == .Array and peer_info.pointee_type.zigTypeTag() == .Array) { + const elem_ty_a = ptr_info.pointee_type.childType(); + const elem_ty_b = peer_info.pointee_type.childType(); + if (try sema.resolvePairInMemoryCoercible(block, src, elem_ty_a, elem_ty_b)) |elem_ty| { + // *[n:x]T + *[n:y]T = *[n]T + if (ptr_info.pointee_type.arrayLen() == peer_info.pointee_type.arrayLen()) { + ptr_info.pointee_type = try Type.array( + sema.arena, + ptr_info.pointee_type.arrayLen(), + null, + elem_ty, + sema.mod, + ); + break :good; + } + // *[a]T + *[b]T = []T + ptr_info.size = .Slice; + ptr_info.pointee_type = elem_ty; + break :good; } - } else { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; } - } else { - if (chosen_ok) { - continue; - } else { - // They're both bad. Report error. - // In the future we probably want to use the - // coerceInMemoryAllowed error reporting mechanism, - // however, for now we just fall through for the - // "incompatible types" error below. + + return generic_err; + }, + .Many => { + // Only works for *[n]T + [*]T -> [*]T, + if (peer_info.pointee_type.zigTypeTag() != .Array) return generic_err; + const elem_ty = peer_info.pointee_type.childType(); + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, elem_ty)) |pointee| { + ptr_info.pointee_type = pointee; + break :good; } - } - } - }, - .Int, .ComptimeInt => { - if (cand_info.size == .C) { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } - }, - .Optional => { - var opt_child_buf: Type.Payload.ElemType = undefined; - const chosen_ptr_ty = chosen_ty.optionalChild(&opt_child_buf); - if (chosen_ptr_ty.zigTypeTag() == .Pointer) { - const chosen_info = chosen_ptr_ty.ptrInfo().data; - - seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; - - // *[N]T to ?![*]T - // *[N]T to ?![]T - if (cand_info.size == .One and - cand_info.pointee_type.zigTypeTag() == .Array and - (chosen_info.size == .Many or chosen_info.size == .Slice)) - { - continue; - } - } - }, - .ErrorUnion => { - const chosen_ptr_ty = chosen_ty.errorUnionPayload(); - if (chosen_ptr_ty.zigTypeTag() == .Pointer) { - const chosen_info = chosen_ptr_ty.ptrInfo().data; - - seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; - - // *[N]T to E![*]T - // *[N]T to E![]T - if (cand_info.size == .One and - cand_info.pointee_type.zigTypeTag() == .Array and - (chosen_info.size == .Many or chosen_info.size == .Slice)) - { - continue; - } - } - }, - .Fn => { - if (!cand_info.mutable and cand_info.pointee_type.zigTypeTag() == .Fn and .ok == try sema.coerceInMemoryAllowedFns(block, chosen_ty, cand_info.pointee_type, target, src, src)) { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - } - }, - else => {}, - } - }, - .Optional => { - var opt_child_buf: Type.Payload.ElemType = undefined; - const opt_child_ty = candidate_ty.optionalChild(&opt_child_buf); - if ((try sema.coerceInMemoryAllowed(block, chosen_ty, opt_child_ty, false, target, src, src)) == .ok) { - seen_const = seen_const or opt_child_ty.isConstPtr(); - any_are_null = true; - continue; + return generic_err; + }, + .Slice => { + // Only works for *[n]T + []T -> []T + if (peer_info.pointee_type.zigTypeTag() != .Array) return generic_err; + const elem_ty = peer_info.pointee_type.childType(); + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, elem_ty)) |pointee| { + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .C => unreachable, + }, + .Many => switch (ptr_info.size) { + .One => { + // Only works for [*]T + *[n]T -> [*]T + if (ptr_info.pointee_type.zigTypeTag() != .Array) return generic_err; + const elem_ty = ptr_info.pointee_type.childType(); + if (try sema.resolvePairInMemoryCoercible(block, src, elem_ty, peer_info.pointee_type)) |pointee| { + ptr_info.size = .Many; + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .Many => { + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, peer_info.pointee_type)) |pointee| { + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .Slice => { + // Only works if no peers are actually slices + if (seen_slice) return generic_err; + // Okay, then works for [*]T + "[]T" -> [*]T + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, peer_info.pointee_type)) |pointee| { + ptr_info.size = .Many; + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .C => unreachable, + }, + .Slice => switch (ptr_info.size) { + .One => { + // Only works for []T + *[n]T -> []T + if (ptr_info.pointee_type.zigTypeTag() != .Array) return generic_err; + const elem_ty = ptr_info.pointee_type.childType(); + if (try sema.resolvePairInMemoryCoercible(block, src, elem_ty, peer_info.pointee_type)) |pointee| { + ptr_info.size = .Slice; + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .Many => { + // Impossible! (current peer is an actual slice) + return generic_err; + }, + .Slice => { + if (try sema.resolvePairInMemoryCoercible(block, src, ptr_info.pointee_type, peer_info.pointee_type)) |pointee| { + ptr_info.pointee_type = pointee; + break :good; + } + return generic_err; + }, + .C => unreachable, + }, + .C => unreachable, + } } - seen_const = seen_const or chosen_ty.isConstPtr(); - any_are_null = false; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - .Vector => switch (chosen_ty_tag) { - .Vector => { - const chosen_len = chosen_ty.vectorLen(); - const candidate_len = candidate_ty.vectorLen(); - if (chosen_len != candidate_len) - continue; + const sentinel_ty = if (ptr_info.size == .One and ptr_info.pointee_type.zigTypeTag() == .Array) blk: { + break :blk ptr_info.pointee_type.childType(); + } else ptr_info.pointee_type; - const chosen_child_ty = chosen_ty.childType(); - const candidate_child_ty = candidate_ty.childType(); - if (chosen_child_ty.zigTypeTag() == .Int and candidate_child_ty.zigTypeTag() == .Int) { - const chosen_info = chosen_child_ty.intInfo(target); - const candidate_info = candidate_child_ty.intInfo(target); - if (chosen_info.bits < candidate_info.bits) { - chosen = candidate; - chosen_i = candidate_i + 1; - } - continue; + // TODO: once InternPool is in, we need to cast the sentinels to sentinel_ty + + if (peer_sentinel != null and cur_sentinel != null and peer_sentinel.?.eql(cur_sentinel.?, sentinel_ty, sema.mod)) { + // Set sentinel + if (ptr_info.size == .One) { + assert(ptr_info.pointee_type.zigTypeTag() == .Array); + ptr_info.pointee_type = try Type.array( + sema.arena, + ptr_info.pointee_type.arrayLen(), + cur_sentinel, + ptr_info.pointee_type.childType(), + sema.mod, + ); + ptr_info.sentinel = null; + } else { + ptr_info.sentinel = cur_sentinel.?; } - if (chosen_child_ty.zigTypeTag() == .Float and candidate_child_ty.zigTypeTag() == .Float) { - if (chosen_ty.floatBits(target) < candidate_ty.floatBits(target)) { - chosen = candidate; - chosen_i = candidate_i + 1; - } - continue; + } else { + // Clear existing sentinel + ptr_info.sentinel = null; + if (ptr_info.pointee_type.zigTypeTag() == .Array) { + ptr_info.pointee_type = try Type.array( + sema.arena, + ptr_info.pointee_type.arrayLen(), + null, + ptr_info.pointee_type.childType(), + sema.mod, + ); } - }, - .Array => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - else => {}, - }, - .Array => switch (chosen_ty_tag) { - .Vector => continue, - else => {}, - }, - .Fn => if (chosen_ty.isSinglePointer() and chosen_ty.isConstPtr() and chosen_ty.childType().zigTypeTag() == .Fn) { - if (.ok == try sema.coerceInMemoryAllowedFns(block, chosen_ty.childType(), candidate_ty, target, src, src)) { - continue; } - }, - else => {}, - } - switch (chosen_ty_tag) { - .NoReturn, .Undefined => { - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - .Null => { - any_are_null = true; - chosen = candidate; - chosen_i = candidate_i + 1; - continue; - }, - .Optional => { - var opt_child_buf: Type.Payload.ElemType = undefined; - const opt_child_ty = chosen_ty.optionalChild(&opt_child_buf); - if ((try sema.coerceInMemoryAllowed(block, opt_child_ty, candidate_ty, false, target, src, src)) == .ok) { + opt_ptr_info = ptr_info; + } + + return .{ .success = try Type.ptr(sema.arena, sema.mod, opt_ptr_info.?) }; + }, + + .func => { + var opt_cur_ty: ?Type = null; + var first_idx: usize = undefined; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + const cur_ty = opt_cur_ty orelse { + opt_cur_ty = ty; + first_idx = i; + continue; + }; + if (ty.zigTypeTag() != .Fn) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + // ty -> cur_ty + if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, target, src, src)) { continue; } - if ((try sema.coerceInMemoryAllowed(block, candidate_ty, opt_child_ty, false, target, src, src)) == .ok) { - any_are_null = true; - chosen = candidate; - chosen_i = candidate_i + 1; + // cur_ty -> ty + if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, target, src, src)) { + opt_cur_ty = ty; continue; } - }, - .ErrorUnion => { - const payload_ty = chosen_ty.errorUnionPayload(); - if ((try sema.coerceInMemoryAllowed(block, payload_ty, candidate_ty, false, target, src, src)) == .ok) { + return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + } + return .{ .success = opt_cur_ty.? }; + }, + + .enum_or_union => { + var opt_cur_ty: ?Type = null; + // The peer index which gave the current type + var cur_ty_idx: usize = undefined; + + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag()) { + .EnumLiteral, .Enum, .Union => {}, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + const cur_ty = opt_cur_ty orelse { + opt_cur_ty = ty; + cur_ty_idx = i; continue; + }; + + // We want to return this in a lot of cases, so alias it here for convenience + const generic_err: PeerResolveResult = .{ .conflict = .{ + .peer_idx_a = cur_ty_idx, + .peer_idx_b = i, + } }; + + switch (cur_ty.zigTypeTag()) { + .EnumLiteral => { + opt_cur_ty = ty; + cur_ty_idx = i; + }, + .Enum => switch (ty.zigTypeTag()) { + .EnumLiteral => {}, + .Enum => { + if (!ty.eql(cur_ty, sema.mod)) return generic_err; + }, + .Union => { + const tag_ty = ty.unionTagTypeHypothetical(); + if (!tag_ty.eql(cur_ty, sema.mod)) return generic_err; + opt_cur_ty = ty; + cur_ty_idx = i; + }, + else => unreachable, + }, + .Union => switch (ty.zigTypeTag()) { + .EnumLiteral => {}, + .Enum => { + const cur_tag_ty = cur_ty.unionTagTypeHypothetical(); + if (!ty.eql(cur_tag_ty, sema.mod)) return generic_err; + }, + .Union => { + if (!ty.eql(cur_ty, sema.mod)) return generic_err; + }, + else => unreachable, + }, + else => unreachable, } - }, - .ErrorSet => { - chosen = candidate; - chosen_i = candidate_i + 1; - if (err_set_ty) |chosen_set_ty| { - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, chosen_ty, src, src)) { - continue; - } - if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_ty, chosen_set_ty, src, src)) { - err_set_ty = chosen_ty; + } + return .{ .success = opt_cur_ty.? }; + }, + + .comptime_int => { + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag()) { + .ComptimeInt => {}, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + } + return .{ .success = Type.comptime_int }; + }, + + .comptime_float => { + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag()) { + .ComptimeInt, .ComptimeFloat => {}, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + } + return .{ .success = Type.initTag(.comptime_float) }; + }, + + .fixed_int => { + var idx_unsigned: ?usize = null; + var idx_signed: ?usize = null; + + for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| { + const ty = opt_ty orelse continue; + + const peer_tag = ty.zigTypeTag(); + switch (peer_tag) { + .ComptimeInt => { + // If the value is undefined, we can't refine to a fixed-width int + if (opt_val == null or opt_val.?.isUndef()) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + try sema.resolveLazyValue(opt_val.?); continue; - } + }, + .Int => {}, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, chosen_ty); - continue; - } else { - err_set_ty = chosen_ty; + const info = ty.intInfo(target); + + const idx_ptr = switch (info.signedness) { + .unsigned => &idx_unsigned, + .signed => &idx_signed, + }; + + const largest_idx = idx_ptr.* orelse { + idx_ptr.* = i; continue; + }; + + const cur_info = peer_tys[largest_idx].?.intInfo(target); + if (info.bits > cur_info.bits) { + idx_ptr.* = i; } - }, - else => {}, - } + } - // At this point, we hit a compile error. We need to recover - // the source locations. - const chosen_src = candidate_srcs.resolve( - sema.gpa, - sema.mod.declPtr(block.src_decl), - chosen_i, - ); - const candidate_src = candidate_srcs.resolve( - sema.gpa, - sema.mod.declPtr(block.src_decl), - candidate_i + 1, - ); + if (idx_signed == null) { + return .{ .success = peer_tys[idx_unsigned.?].? }; + } - const msg = msg: { - const msg = try sema.errMsg(block, src, "incompatible types: '{}' and '{}'", .{ - chosen_ty.fmt(sema.mod), - candidate_ty.fmt(sema.mod), - }); - errdefer msg.destroy(sema.gpa); + if (idx_unsigned == null) { + return .{ .success = peer_tys[idx_signed.?].? }; + } - if (chosen_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{chosen_ty.fmt(sema.mod)}); + const unsigned_info = peer_tys[idx_unsigned.?].?.intInfo(target); + const signed_info = peer_tys[idx_signed.?].?.intInfo(target); + if (signed_info.bits > unsigned_info.bits) { + return .{ .success = peer_tys[idx_signed.?].? }; + } - if (candidate_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{candidate_ty.fmt(sema.mod)}); + return .{ .conflict = .{ + .peer_idx_a = idx_unsigned.?, + .peer_idx_b = idx_signed.?, + } }; + }, - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } + .fixed_float => { + var opt_cur_ty: ?Type = null; + + for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag()) { + .ComptimeFloat, .ComptimeInt => {}, + .Int => { + if (opt_val == null) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + }, + .Float => { + if (opt_cur_ty) |cur_ty| { + if (cur_ty.eql(ty, sema.mod)) continue; + // Recreate the type so we eliminate any c_longdouble + const bits = @max(cur_ty.floatBits(target), ty.floatBits(target)); + opt_cur_ty = switch (bits) { + 16 => Type.f16, + 32 => Type.f32, + 64 => Type.f64, + 80 => Type.f80, + 128 => Type.f128, + else => unreachable, + }; + } else { + opt_cur_ty = ty; + } + }, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + } - const chosen_ty = sema.typeOf(chosen); + // Note that fixed_float is only chosen if there is at least one fixed-width float peer, + // so opt_cur_ty must be non-null. + return .{ .success = opt_cur_ty.? }; + }, - if (convert_to_slice) { - // turn *[N]T => []T - const chosen_child_ty = chosen_ty.childType(); - var info = chosen_ty.ptrInfo(); - info.data.sentinel = chosen_child_ty.sentinel(); - info.data.size = .Slice; - info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr()); - info.data.pointee_type = chosen_child_ty.elemType2(); + .exact => { + var expect_ty: ?Type = null; + var first_idx: usize = undefined; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + if (expect_ty) |expect| { + if (!ty.eql(expect, sema.mod)) return .{ .conflict = .{ + .peer_idx_a = first_idx, + .peer_idx_b = i, + } }; + } else { + expect_ty = ty; + first_idx = i; + } + } + return .{ .success = expect_ty.? }; + }, + } +} - const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); - const opt_ptr_ty = if (any_are_null) - try Type.optional(sema.arena, new_ptr_ty) - else - new_ptr_ty; - const set_ty = err_set_ty orelse return opt_ptr_ty; - return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); +fn maybeMergeErrorSets(sema: *Sema, block: *Block, src: LazySrcLoc, e0: Type, e1: Type) !Type { + // e0 -> e1 + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, e1, e0, src, src)) { + return e1; } - if (seen_const) { - // turn []T => []const T - switch (chosen_ty.zigTypeTag()) { - .ErrorUnion => { - const ptr_ty = chosen_ty.errorUnionPayload(); - var info = ptr_ty.ptrInfo(); - info.data.mutable = false; - const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); - const opt_ptr_ty = if (any_are_null) - try Type.optional(sema.arena, new_ptr_ty) - else - new_ptr_ty; - const set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); - return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); - }, - .Pointer => { - var info = chosen_ty.ptrInfo(); - info.data.mutable = false; - const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); - const opt_ptr_ty = if (any_are_null) - try Type.optional(sema.arena, new_ptr_ty) - else - new_ptr_ty; - const set_ty = err_set_ty orelse return opt_ptr_ty; - return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); - }, - else => return chosen_ty, - } + // e1 -> e0 + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, e0, e1, src, src)) { + return e0; } - if (any_are_null) { - const opt_ty = switch (chosen_ty.zigTypeTag()) { - .Null, .Optional => chosen_ty, - else => try Type.optional(sema.arena, chosen_ty), - }; - const set_ty = err_set_ty orelse return opt_ty; - return try Type.errorUnion(sema.arena, set_ty, opt_ty, sema.mod); + return e0.errorSetMerge(sema.arena, e1); +} + +fn resolvePairInMemoryCoercible(sema: *Sema, block: *Block, src: LazySrcLoc, ty_a: Type, ty_b: Type) !?Type { + // ty_b -> ty_a + if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, true, sema.mod.getTarget(), src, src)) { + return ty_a; } - if (err_set_ty) |ty| switch (chosen_ty.zigTypeTag()) { - .ErrorSet => return ty, - .ErrorUnion => { - const payload_ty = chosen_ty.errorUnionPayload(); - return try Type.errorUnion(sema.arena, ty, payload_ty, sema.mod); - }, - else => return try Type.errorUnion(sema.arena, ty, chosen_ty, sema.mod), - }; + // ty_a -> ty_b + if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, true, sema.mod.getTarget(), src, src)) { + return ty_b; + } - return chosen_ty; + return null; } pub fn resolveFnTypes(sema: *Sema, fn_info: Type.Payload.Function.Data) CompileError!void { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a346ec756fee..0482c38a0853 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3110,7 +3110,7 @@ fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, tab log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ base_offset + offset, self.getSymbolName(entry), - @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER), + bind_sym.getDylibOrdinal(), }); if (bind_sym.weakRef()) { log.debug(" | marking as weak ref ", .{}); @@ -3144,10 +3144,7 @@ fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void { for (bindings.items) |binding| { const bind_sym = self.getSymbol(binding.target); const bind_sym_name = self.getSymbolName(binding.target); - const dylib_ordinal = @divTrunc( - @bitCast(i16, bind_sym.n_desc), - macho.N_SYMBOL_RESOLVER, - ); + const dylib_ordinal = bind_sym.getDylibOrdinal(); log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ binding.offset + base_offset, bind_sym_name, @@ -4145,7 +4142,7 @@ pub fn logSymtab(self: *MachO) void { for (self.locals.items, 0..) |sym, sym_id| { const where = if (sym.undf() and !sym.tentative()) "ord" else "sect"; const def_index = if (sym.undf() and !sym.tentative()) - @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER) + sym.getDylibOrdinal() else sym.n_sect + 1; log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ diff --git a/src/link/MachO/dyld_info/bind.zig b/src/link/MachO/dyld_info/bind.zig index 98a693920a6f..9638ff65b160 100644 --- a/src/link/MachO/dyld_info/bind.zig +++ b/src/link/MachO/dyld_info/bind.zig @@ -95,7 +95,7 @@ pub fn Bind(comptime Ctx: type, comptime Target: type) type { const sym = ctx.getSymbol(current.target); const name = ctx.getSymbolName(current.target); const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; - const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const ordinal = sym.getDylibOrdinal(); try setSymbol(name, flags, writer); try setTypePointer(writer); @@ -213,7 +213,7 @@ pub fn LazyBind(comptime Ctx: type, comptime Target: type) type { const sym = ctx.getSymbol(entry.target); const name = ctx.getSymbolName(entry.target); const flags: u8 = if (sym.weakRef()) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; - const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const ordinal = sym.getDylibOrdinal(); try setSegmentOffset(entry.segment_id, entry.offset, writer); try setSymbol(name, flags, writer); @@ -341,7 +341,7 @@ const TestContext = struct { fn addSymbol(ctx: *TestContext, gpa: Allocator, name: []const u8, ordinal: i16, flags: u16) !void { const n_strx = try ctx.addString(gpa, name); - var n_desc = @bitCast(u16, ordinal * macho.N_SYMBOL_RESOLVER); + var n_desc = @bitCast(u16, ordinal) * macho.N_SYMBOL_RESOLVER; n_desc |= flags; try ctx.symbols.append(gpa, .{ .n_value = 0, diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 7e6870ecbc23..d8509d5b71e6 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -1881,7 +1881,7 @@ pub const Zld = struct { const sym = entry.getAtomSymbol(self); const base_offset = sym.n_value - seg.vmaddr; - const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER); + const dylib_ordinal = bind_sym.getDylibOrdinal(); log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ base_offset, bind_sym_name, @@ -1986,7 +1986,7 @@ pub const Zld = struct { const offset = @intCast(u64, base_offset + rel_offset); const addend = mem.readIntLittle(i64, code[rel_offset..][0..8]); - const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER); + const dylib_ordinal = bind_sym.getDylibOrdinal(); log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ base_offset, bind_sym_name, @@ -2038,7 +2038,7 @@ pub const Zld = struct { const stub_entry = self.stubs.items[count]; const bind_sym = stub_entry.getTargetSymbol(self); const bind_sym_name = stub_entry.getTargetSymbolName(self); - const dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER); + const dylib_ordinal = bind_sym.getDylibOrdinal(); log.debug(" | lazy bind at {x}, import('{s}') in dylib({d})", .{ base_offset, bind_sym_name, @@ -3243,7 +3243,7 @@ pub const Zld = struct { const sym = self.getSymbol(global); if (!sym.undf()) continue; if (sym.n_desc == N_DEAD) continue; - const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER); + const ord = sym.getDylibOrdinal(); scoped_log.debug(" %{d}: {s} @{x} in ord({d}), {s}", .{ i, self.getSymbolName(global), diff --git a/src/type.zig b/src/type.zig index bcbb9e2ea263..0adf4def3956 100644 --- a/src/type.zig +++ b/src/type.zig @@ -6779,7 +6779,7 @@ pub const Type = extern union { const x = val.castTag(.int_i64).?.data; if (x >= 0) return smallestUnsignedBits(@intCast(u64, x)); assert(sign); - return smallestUnsignedBits(@intCast(u64, -x - 1)) + 1; + return smallestUnsignedBits(@intCast(u64, -(x + 1))) + 1; }, else => { const x = val.toUnsignedInt(target); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index d6717032ff16..1a452fce0cd8 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1622,3 +1622,156 @@ test "coercion from single-item pointer to @as to slice" { try expect(t[0] == 1); } + +test "peer type resolution: const sentinel slice and mutable non-sentinel slice" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest(comptime T: type, comptime s: T) !void { + var a: [:s]const T = undefined; + var b: []T = undefined; + try expect(@TypeOf(a, b) == []const T); + try expect(@TypeOf(b, a) == []const T); + } + }; + + try S.doTheTest(u8, 0); + try S.doTheTest(?*anyopaque, null); + try comptime S.doTheTest(u8, 0); + try comptime S.doTheTest(?*anyopaque, null); +} + +test "peer type resolution: float and comptime-known fixed-width integer" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + const i: u8 = 100; + var f: f32 = 1.23; + try expect(@TypeOf(i, f) == f32); + try expect(@TypeOf(f, i) == f32); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: same array type with sentinel" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var a: [2:0]u32 = .{ 0, 1 }; + var b: [2:0]u32 = .{ 2, 3 }; + try expect(@TypeOf(a, b) == [2:0]u32); + try expect(@TypeOf(b, a) == [2:0]u32); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: array with sentinel and array without sentinel" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var a: [2:0]u32 = .{ 0, 1 }; + var b: [2]u32 = .{ 2, 3 }; + try expect(@TypeOf(a, b) == [2]u32); + try expect(@TypeOf(b, a) == [2]u32); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: array and vector with same child type" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var arr: [2]u32 = .{ 0, 1 }; + var vec: @Vector(2, u32) = .{ 2, 3 }; + try expect(@TypeOf(arr, vec) == @Vector(2, u32)); + try expect(@TypeOf(vec, arr) == @Vector(2, u32)); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: array with smaller child type and vector with larger child type" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var arr: [2]u8 = .{ 0, 1 }; + var vec: @Vector(2, u64) = .{ 2, 3 }; + try expect(@TypeOf(arr, vec) == @Vector(2, u64)); + try expect(@TypeOf(vec, arr) == @Vector(2, u64)); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: error union and optional of same type" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + const E = error{Foo}; + var a: E!*u8 = error.Foo; + var b: ?*u8 = null; + try expect(@TypeOf(a, b) == E!?*u8); + try expect(@TypeOf(b, a) == E!?*u8); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: C pointer and @TypeOf(null)" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var a: [*c]c_int = undefined; + const b = null; + try expect(@TypeOf(a, b) == [*c]c_int); + try expect(@TypeOf(b, a) == [*c]c_int); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + +test "peer type resolution: three-way resolution combines error set and optional" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + const E = error{Foo}; + var a: E = undefined; + var b: ?*const [5:0]u8 = undefined; + var c: [*:0]u8 = undefined; + try expect(@TypeOf(a, b, c) == E!?[*:0]const u8); + try expect(@TypeOf(a, c, b) == E!?[*:0]const u8); + try expect(@TypeOf(b, a, c) == E!?[*:0]const u8); + try expect(@TypeOf(b, c, a) == E!?[*:0]const u8); + try expect(@TypeOf(c, a, b) == E!?[*:0]const u8); + try expect(@TypeOf(c, b, a) == E!?[*:0]const u8); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} diff --git a/test/cases/compile_errors/compare_optional_to_non-optional_with_invalid_types.zig b/test/cases/compile_errors/compare_optional_to_non-optional_with_invalid_types.zig index 0339ac034e48..82713e358bc9 100644 --- a/test/cases/compile_errors/compare_optional_to_non-optional_with_invalid_types.zig +++ b/test/cases/compile_errors/compare_optional_to_non-optional_with_invalid_types.zig @@ -1,4 +1,4 @@ -export fn inconsistentChildType() void { +export fn compatibleChildType() void { var x: ?i32 = undefined; const y: comptime_int = 10; _ = (x == y); @@ -28,9 +28,6 @@ export fn invalidChildType() void { // backend=llvm // target=native // -// :4:12: error: incompatible types: '?i32' and 'comptime_int' -// :4:10: note: type '?i32' here -// :4:15: note: type 'comptime_int' here // :19:12: error: incompatible types: '?@Vector(10, i32)' and '@Vector(11, i32)' // :19:10: note: type '?@Vector(10, i32)' here // :19:15: note: type '@Vector(11, i32)' here