From 7ebaf08ca7e1b45608daf30d4876a0541e1a725e 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: #15644 Resolves: #15693 Resolves: #15709 --- src/Sema.zig | 1583 +++++++++++------ src/type.zig | 2 +- test/behavior/cast.zig | 283 +++ ...nal_to_non-optional_with_invalid_types.zig | 26 +- 4 files changed, 1350 insertions(+), 544 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 39b7444e2854..802dfd2de4a3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -26751,15 +26751,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( @@ -30190,6 +30188,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, @@ -30203,590 +30420,914 @@ fn resolvePeerTypes( else => {}, } - const target = sema.mod.getTarget(); + var peer_tys = try sema.arena.alloc(?Type, instructions.len); + var peer_vals = try sema.arena.alloc(?Value, instructions.len); - 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, + for (instructions, peer_tys, peer_vals) |inst, *ty, *val| { + ty.* = sema.typeOf(inst); + val.* = try sema.resolveMaybeUndefVal(inst); + } - .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; + }, + } + + 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.? }; + }, + + .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.* = 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, + } }; + }; + + 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; + } - 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; + // 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, + } }; + } - 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; - } + 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, + } }; + } - const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); - const candidate_set_ty = candidate_ty.errorUnionSet(); + ptr_info.mutable = ptr_info.mutable and peer_info.mutable; + ptr_info.@"volatile" = ptr_info.@"volatile" or peer_info.@"volatile"; - 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; - } - }, + opt_ptr_info = ptr_info; + } + return .{ .success = try Type.ptr(sema.arena, sema.mod, opt_ptr_info.?) }; + }, - 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; + .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; + } + 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; + + // TODO: this is for compatibility with legacy behavior. See beneath the loop. + var any_comptime_known = false; + + 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, + } }; + any_comptime_known = true; + 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; + if (opt_val != null) any_comptime_known = true; + + 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)}); + // TODO: this is for compatibility with legacy behavior. Before this version of PTR was + // implemented, the algorithm very often returned false positives, with the expectation + // that you'd just hit a coercion error later. One of these was that for integers, the + // largest type would always be returned, even if it couldn't fit everything. This had + // an unintentional consequence to semantics, which is that if values were known at + // comptime, they would be coerced down to the smallest type where possible. This + // behavior is unintuitive and order-dependent, so in my opinion should be eliminated, + // but for now we'll retain compatibility. + if (any_comptime_known) { + if (unsigned_info.bits > signed_info.bits) { + return .{ .success = peer_tys[idx_unsigned.?].? }; + } + const idx = @min(idx_unsigned.?, idx_signed.?); + return .{ .success = peer_tys[idx].? }; + } - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - } + return .{ .conflict = .{ + .peer_idx_a = idx_unsigned.?, + .peer_idx_b = idx_signed.?, + } }; + }, - const chosen_ty = sema.typeOf(chosen); + .fixed_float => { + var opt_cur_ty: ?Type = null; - 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(); + 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 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); + // 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.? }; + }, + + .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.? }; + }, } +} - 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, - } +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 (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); + // e1 -> e0 + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, e0, e1, src, src)) { + return e0; } - 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), - }; + 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; + } - return chosen_ty; + // ty_a -> ty_b + if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, true, sema.mod.getTarget(), src, src)) { + return ty_b; + } + + return null; } pub fn resolveFnTypes(sema: *Sema, fn_info: Type.Payload.Function.Data) CompileError!void { 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 847abc17981f..e72bf28b26b3 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -1,6 +1,8 @@ const builtin = @import("builtin"); const std = @import("std"); +const assert = std.debug.assert; const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; const mem = std.mem; const maxInt = std.math.maxInt; const native_endian = builtin.target.cpu.arch.endian(); @@ -1612,3 +1614,284 @@ 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_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const S = struct { + fn doTheTest(comptime T: type, comptime s: T) !void { + var a: [:s]const T = @intToPtr(*const [2:s]T, 0x1000); + var b: []T = @intToPtr(*[3]T, 0x2000); + comptime assert(@TypeOf(a, b) == []const T); + comptime assert(@TypeOf(b, a) == []const T); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const R = @TypeOf(r1); + + try expectEqual(@as(R, @intToPtr(*const [2:s]T, 0x1000)), r1); + try expectEqual(@as(R, @intToPtr(*const [3]T, 0x2000)), r2); + } + }; + + try S.doTheTest(u8, 0); + try S.doTheTest(?*anyopaque, null); +} + +test "peer type resolution: float and comptime-known fixed-width integer" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const i: u8 = 100; + var f: f32 = 1.234; + comptime assert(@TypeOf(i, f) == f32); + comptime assert(@TypeOf(f, i) == f32); + + var t = true; + const r1 = if (t) i else f; + const r2 = if (t) f else i; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, 100.0), r1); + try expectEqual(@as(T, 1.234), r2); +} + +test "peer type resolution: same array type with sentinel" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var a: [2:0]u32 = .{ 0, 1 }; + var b: [2:0]u32 = .{ 2, 3 }; + comptime assert(@TypeOf(a, b) == [2:0]u32); + comptime assert(@TypeOf(b, a) == [2:0]u32); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(T{ 0, 1 }, r1); + try expectEqual(T{ 2, 3 }, r2); +} + +test "peer type resolution: array with sentinel and array without sentinel" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var a: [2:0]u32 = .{ 0, 1 }; + var b: [2]u32 = .{ 2, 3 }; + comptime assert(@TypeOf(a, b) == [2]u32); + comptime assert(@TypeOf(b, a) == [2]u32); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(T{ 0, 1 }, r1); + try expectEqual(T{ 2, 3 }, r2); +} + +test "peer type resolution: array and vector with same child type" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var arr: [2]u32 = .{ 0, 1 }; + var vec: @Vector(2, u32) = .{ 2, 3 }; + comptime assert(@TypeOf(arr, vec) == @Vector(2, u32)); + comptime assert(@TypeOf(vec, arr) == @Vector(2, u32)); + + var t = true; + const r1 = if (t) arr else vec; + const r2 = if (t) vec else arr; + + const T = @TypeOf(r1); + + try expectEqual(T{ 0, 1 }, r1); + try expectEqual(T{ 2, 3 }, r2); +} + +test "peer type resolution: array with smaller child type and vector with larger child type" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var arr: [2]u8 = .{ 0, 1 }; + var vec: @Vector(2, u64) = .{ 2, 3 }; + comptime assert(@TypeOf(arr, vec) == @Vector(2, u64)); + comptime assert(@TypeOf(vec, arr) == @Vector(2, u64)); + + var t = true; + const r1 = if (t) arr else vec; + const r2 = if (t) vec else arr; + + const T = @TypeOf(r1); + + try expectEqual(T{ 0, 1 }, r1); + try expectEqual(T{ 2, 3 }, r2); +} + +test "peer type resolution: error union and optional of same type" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const E = error{Foo}; + var a: E!*u8 = error.Foo; + var b: ?*u8 = null; + comptime assert(@TypeOf(a, b) == E!?*u8); + comptime assert(@TypeOf(b, a) == E!?*u8); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, error.Foo), r1); + try expectEqual(@as(T, null), r2); +} + +test "peer type resolution: C pointer and @TypeOf(null)" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var a: [*c]c_int = 0x1000; + const b = null; + comptime assert(@TypeOf(a, b) == [*c]c_int); + comptime assert(@TypeOf(b, a) == [*c]c_int); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, 0x1000), r1); + try expectEqual(@as(T, null), r2); +} + +test "peer type resolution: three-way resolution combines error set and optional" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + const E = error{Foo}; + var a: E = error.Foo; + var b: *const [5:0]u8 = @intToPtr(*const [5:0]u8, 0x1000); + var c: ?[*:0]u8 = null; + comptime assert(@TypeOf(a, b, c) == E!?[*:0]const u8); + comptime assert(@TypeOf(a, c, b) == E!?[*:0]const u8); + comptime assert(@TypeOf(b, a, c) == E!?[*:0]const u8); + comptime assert(@TypeOf(b, c, a) == E!?[*:0]const u8); + comptime assert(@TypeOf(c, a, b) == E!?[*:0]const u8); + comptime assert(@TypeOf(c, b, a) == E!?[*:0]const u8); + + var x: u8 = 0; + const r1 = switch (x) { + 0 => a, + 1 => b, + else => c, + }; + const r2 = switch (x) { + 0 => b, + 1 => a, + else => c, + }; + const r3 = switch (x) { + 0 => c, + 1 => a, + else => b, + }; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, error.Foo), r1); + try expectEqual(@as(T, @intToPtr([*:0]u8, 0x1000)), r2); + try expectEqual(@as(T, null), r3); +} + +test "peer type resolution: vector and optional vector" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var a: ?@Vector(3, u32) = .{ 0, 1, 2 }; + var b: @Vector(3, u32) = .{ 3, 4, 5 }; + comptime assert(@TypeOf(a, b) == ?@Vector(3, u32)); + comptime assert(@TypeOf(b, a) == ?@Vector(3, u32)); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, .{ 0, 1, 2 }), r1); + try expectEqual(@as(T, .{ 3, 4, 5 }), r2); +} + +test "peer type resolution: optional fixed-width int and comptime_int" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO + + var a: ?i32 = 42; + const b: comptime_int = 50; + comptime assert(@TypeOf(a, b) == ?i32); + comptime assert(@TypeOf(b, a) == ?i32); + + var t = true; + const r1 = if (t) a else b; + const r2 = if (t) b else a; + + const T = @TypeOf(r1); + + try expectEqual(@as(T, 42), r1); + try expectEqual(@as(T, 50), r2); +} 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..c42ab3a2afa8 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,19 +1,4 @@ -export fn inconsistentChildType() void { - var x: ?i32 = undefined; - const y: comptime_int = 10; - _ = (x == y); -} -export fn optionalToOptional() void { - var x: ?i32 = undefined; - var y: ?i32 = undefined; - _ = (x == y); -} export fn optionalVector() void { - var x: ?@Vector(10, i32) = undefined; - var y: @Vector(10, i32) = undefined; - _ = (x == y); -} -export fn optionalVector2() void { var x: ?@Vector(10, i32) = undefined; var y: @Vector(11, i32) = undefined; _ = (x == y); @@ -28,10 +13,7 @@ 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 -// :24:12: error: operator == not allowed for type '?[3]i32' +// :4:12: error: incompatible types: '?@Vector(10, i32)' and '@Vector(11, i32)' +// :4:10: note: type '?@Vector(10, i32)' here +// :4:15: note: type '@Vector(11, i32)' here +// :9:12: error: operator == not allowed for type '?[3]i32'