From 95d98b4f1b16166bbc8a99ddc411454267d2fe0d 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/fmt/parse_float/parse.zig | 2 +- lib/std/macho.zig | 4 + src/Sema.zig | 1559 ++++++++++++++++++---------- 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/bugs/4328.zig | 3 +- test/behavior/cast.zig | 193 ++++ 11 files changed, 1250 insertions(+), 540 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/fmt/parse_float/parse.zig b/lib/std/fmt/parse_float/parse.zig index 9f6e75b29a59..0d0d6151f592 100644 --- a/lib/std/fmt/parse_float/parse.zig +++ b/lib/std/fmt/parse_float/parse.zig @@ -156,7 +156,7 @@ fn parsePartialNumberBase(comptime T: type, stream: *FloatStream, negative: bool }; } - n_digits -= info.max_mantissa_digits; + n_digits -= @as(isize, info.max_mantissa_digits); var many_digits = false; stream.reset(); // re-parse from beginning while (stream.firstIs3('0', '.', '_')) { diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 8bddd67023ca..0e2fb5ab8802 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -890,6 +890,10 @@ pub const nlist_64 = extern struct { if (!sym.undf()) return false; return sym.n_value != 0; } + + pub fn getDylibOrdinal(sym: nlist_64) i16 { + return @divTrunc(@bitCast(i16, sym.n_desc), @as(u15, N_SYMBOL_RESOLVER)); + } }; /// Format of a relocation entry of a Mach-O file. Modified from the 4.3BSD diff --git a/src/Sema.zig b/src/Sema.zig index 247dea5f6307..b7f1ca435181 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -26739,15 +26739,13 @@ fn coerceInMemoryAllowedErrorSets( return .ok; }, .anyerror => 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,888 @@ 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; - } + var peer_tys = try sema.arena.alloc(?Type, instructions.len); + var peer_vals = try sema.arena.alloc(?Value, instructions.len); - switch (candidate_ty_tag) { - .NoReturn, .Undefined => continue, + for (instructions, peer_tys, peer_vals) |inst, *ty, *val| { + ty.* = sema.typeOf(inst); + if (try sema.resolveMaybeUndefVal(inst)) |inst_val| { + if (!inst_val.isUndef()) { + val.* = inst_val; + } else val.* = null; + } else val.* = null; + } - .Null => { - any_are_null = true; - continue; - }, + var strat_reason: PeerResolveStrategy.Reason = .{ + .peers = try std.DynamicBitSet.initEmpty(sema.arena, instructions.len), + }; - .Int => switch (chosen_ty_tag) { - .ComptimeInt => { - 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); }, - .Int => { - const chosen_info = chosen_ty.intInfo(target); - const candidate_info = candidate_ty.intInfo(target); - - if (chosen_info.bits < candidate_info.bits) { - 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; }, - .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; - }, - 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; + }, + } + + 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.? }; + }, + + .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 }; + }, - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + .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.* = opt_val.optionalValue(); + }, + 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, + }; + + // 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; + } - // [*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; + 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; - continue; - } + } + 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 opt_cur_ty: ?Type = null; + // We keep the int bounds around to avoid unnecessarily recreating them every iteration + var cur_min: Value = undefined; + var cur_max: Value = undefined; + + 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.?.isUndef()) return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }; + try sema.resolveLazyValue(opt_val.?); + }, + .Int => {}, + else => return .{ .bad_strat = .{ + .strat = s, + .peer_idx = i, + } }, + } + + const peer_min = switch (peer_tag) { + .ComptimeInt => opt_val.?, + .Int => try ty.minInt(sema.arena, target), + else => unreachable, + }; + const peer_max = switch (peer_tag) { + .ComptimeInt => opt_val.?, + .Int => try ty.maxInt(sema.arena, target), + else => unreachable, + }; - err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, chosen_ty); + const cur_ty = opt_cur_ty orelse { + opt_cur_ty = ty; + cur_min = peer_min; + cur_max = peer_max; continue; - } else { - err_set_ty = chosen_ty; + }; + + if (peer_tag == .ComptimeInt) { + // If the current type fits this value, don't refine it + // We want this so that e.g. usize + comptime_int = usize rather than u64 + if (try sema.intFitsInType(opt_val.?, cur_ty, null)) continue; + } else if (ty.eql(cur_ty, sema.mod)) { + // e.g. usize + usize = usize continue; } - }, - 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, - ); - 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); + cur_min = if (Value.compareHetero(peer_min, .lt, cur_min, target)) peer_min else cur_min; + cur_max = if (Value.compareHetero(peer_max, .gt, cur_max, target)) peer_max else cur_max; + opt_cur_ty = try Type.intFittingRange(target, sema.arena, cur_min, cur_max); + } - if (chosen_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{chosen_ty.fmt(sema.mod)}); + return .{ .success = opt_cur_ty.? }; + }, - if (candidate_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{candidate_ty.fmt(sema.mod)}); + .fixed_float => { + var opt_cur_ty: ?Type = null; - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } + 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/bugs/4328.zig b/test/behavior/bugs/4328.zig index 89eb4d4fd00f..a5e46a876467 100644 --- a/test/behavior/bugs/4328.zig +++ b/test/behavior/bugs/4328.zig @@ -47,7 +47,8 @@ test "Peer resolution of extern function calls in @TypeOf" { } fn doTheTest() !void { - try expect(@TypeOf(test_fn()) == c_long); + const LongInt = @Type(@typeInfo(c_long)); + try expect(@TypeOf(test_fn()) == LongInt); } }; diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index d6717032ff16..f67efd6253af 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1622,3 +1622,196 @@ 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: signed and unsigned integer" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn test0() !void { + var i: i8 = undefined; + var u: u16 = undefined; + try expect(@TypeOf(i, u) == i17); + try expect(@TypeOf(u, i) == i17); + } + fn test1() !void { + var i: i16 = undefined; + var u: u16 = undefined; + try expect(@TypeOf(i, u) == i17); + try expect(@TypeOf(u, i) == i17); + } + }; + + try S.test0(); + try S.test1(); + try comptime S.test0(); + try comptime S.test1(); +} + +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: array of signed and vector of unsigned" { + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; + + const S = struct { + fn doTheTest() !void { + var arr: [2]u16 = .{ 0, 1 }; + var vec: @Vector(2, i16) = .{ 2, 3 }; + try expect(@TypeOf(arr, vec) == @Vector(2, i17)); + try expect(@TypeOf(vec, arr) == @Vector(2, i17)); + } + }; + + 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(); +}