From 9a858feaed3bb1086144761265c6bc930aa63bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:04:18 -0700 Subject: [PATCH 1/8] Resolve type of empty struct `.{}` --- src/analysis.zig | 2 +- tests/analysis/container.zig | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index a26c8f5f8..4a5861aa6 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2894,7 +2894,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) error .struct_init_dot_two_comma, .struct_init_dot, .struct_init_dot_comma, - => {}, + => return Type.fromIP(analyser, .empty_struct_type, .empty_aggregate), .root, .test_decl, diff --git a/tests/analysis/container.zig b/tests/analysis/container.zig index 6653a4631..1314ed6e6 100644 --- a/tests/analysis/container.zig +++ b/tests/analysis/container.zig @@ -1,3 +1,6 @@ +const empty_struct = .{}; +// ^^^^^^^^^^^^ (@TypeOf(.{}))(.{}) + const Foo = struct { foo: i32 = 0, const bar: i64 = 0; From 1cf592837ff81ee0f18607ae37e8c858f067568a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:41:08 -0700 Subject: [PATCH 2/8] Clean up analysis tests for peer type resolution --- tests/analysis/peer_type_resolution.zig | 199 ++++++++++-------------- 1 file changed, 78 insertions(+), 121 deletions(-) diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index ed9e87d56..4be8ab221 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -7,161 +7,123 @@ const s: S = .{ .float = 1.2, }; -pub fn main() !void { - var runtime_bool: bool = true; +var runtime_bool: bool = true; - const widened_int_0 = if (runtime_bool) @as(i8, 0) else @as(i16, 0); - _ = widened_int_0; - // ^^^^^^^^^^^^^ (i16)() +const widened_int_0 = if (runtime_bool) @as(i8, 0) else @as(i16, 0); +// ^^^^^^^^^^^^^ (i16)() - const widened_int_1 = if (runtime_bool) @as(i16, 0) else @as(i8, 0); - _ = widened_int_1; - // ^^^^^^^^^^^^^ (i16)() +const widened_int_1 = if (runtime_bool) @as(i16, 0) else @as(i8, 0); +// ^^^^^^^^^^^^^ (i16)() - const optional_0 = if (runtime_bool) s else @as(?S, s); - _ = optional_0; - // ^^^^^^^^^^ (?S)() +const optional_0 = if (runtime_bool) s else @as(?S, s); +// ^^^^^^^^^^ (?S)() - const optional_1 = if (runtime_bool) @as(?S, s) else s; - _ = optional_1; - // ^^^^^^^^^^ (?S)() +const optional_1 = if (runtime_bool) @as(?S, s) else s; +// ^^^^^^^^^^ (?S)() - const optional_2 = if (runtime_bool) null else s; - _ = optional_2; - // ^^^^^^^^^^ (?S)() +const optional_2 = if (runtime_bool) null else s; +// ^^^^^^^^^^ (?S)() - const optional_3 = if (runtime_bool) s else null; - _ = optional_3; - // ^^^^^^^^^^ (?S)() +const optional_3 = if (runtime_bool) s else null; +// ^^^^^^^^^^ (?S)() - const optional_4 = if (runtime_bool) null else @as(?S, s); - _ = optional_4; - // ^^^^^^^^^^ (?S)() +const optional_4 = if (runtime_bool) null else @as(?S, s); +// ^^^^^^^^^^ (?S)() - const optional_5 = if (runtime_bool) @as(?S, s) else null; - _ = optional_5; - // ^^^^^^^^^^ (?S)() +const optional_5 = if (runtime_bool) @as(?S, s) else null; +// ^^^^^^^^^^ (?S)() - const error_set_0 = if (runtime_bool) error.A else @as(error{ A, B }, error.A); - _ = error_set_0 catch {}; - // ^^^^^^^^^^^ (error{A,B})() +const error_set_0 = if (runtime_bool) error.A else @as(error{ A, B }, error.A); +// ^^^^^^^^^^^ (error{A,B})() - const error_set_1 = if (runtime_bool) @as(error{ A, B }, error.A) else error.A; - _ = error_set_1 catch {}; - // ^^^^^^^^^^^ (error{A,B})() +const error_set_1 = if (runtime_bool) @as(error{ A, B }, error.A) else error.A; +// ^^^^^^^^^^^ (error{A,B})() - const error_set_2 = if (runtime_bool) error.B else error.A; - _ = error_set_2 catch {}; - // ^^^^^^^^^^^ (error{B,A})() +const error_set_2 = if (runtime_bool) error.B else error.A; +// ^^^^^^^^^^^ (error{B,A})() - const error_set_3 = if (runtime_bool) error.A else error.B; - _ = error_set_3 catch {}; - // ^^^^^^^^^^^ (error{A,B})() +const error_set_3 = if (runtime_bool) error.A else error.B; +// ^^^^^^^^^^^ (error{A,B})() - const error_set_4 = if (runtime_bool) @as(error{ B, C }, error.B) else @as(error{ A, B }, error.A); - _ = error_set_4 catch {}; - // ^^^^^^^^^^^ (error{B,C,A})() +const error_set_4 = if (runtime_bool) @as(error{ B, C }, error.B) else @as(error{ A, B }, error.A); +// ^^^^^^^^^^^ (error{B,C,A})() - const error_set_5 = if (runtime_bool) @as(error{ A, B }, error.A) else @as(error{ B, C }, error.B); - _ = error_set_5 catch {}; - // ^^^^^^^^^^^ (error{A,B,C})() +const error_set_5 = if (runtime_bool) @as(error{ A, B }, error.A) else @as(error{ B, C }, error.B); +// ^^^^^^^^^^^ (error{A,B,C})() - const error_union_0 = if (runtime_bool) s else @as(error{A}!S, s); - _ = error_union_0 catch {}; - // ^^^^^^^^^^^^^ (error{A}!S)() +const error_union_0 = if (runtime_bool) s else @as(error{A}!S, s); +// ^^^^^^^^^^^^^ (error{A}!S)() - const error_union_1 = if (runtime_bool) @as(error{A}!S, s) else s; - _ = error_union_1 catch {}; - // ^^^^^^^^^^^^^ (error{A}!S)() +const error_union_1 = if (runtime_bool) @as(error{A}!S, s) else s; +// ^^^^^^^^^^^^^ (error{A}!S)() - const error_union_2 = if (runtime_bool) @as(?S, s) else @as(error{A}!S, s); - _ = error_union_2 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_2 = if (runtime_bool) @as(?S, s) else @as(error{A}!S, s); +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_3 = if (runtime_bool) @as(error{A}!S, s) else @as(?S, s); - _ = error_union_3 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_3 = if (runtime_bool) @as(error{A}!S, s) else @as(?S, s); +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_4 = if (runtime_bool) null else @as(error{A}!S, s); - _ = error_union_4 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_4 = if (runtime_bool) null else @as(error{A}!S, s); +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_5 = if (runtime_bool) @as(error{A}!S, s) else null; - _ = error_union_5 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_5 = if (runtime_bool) @as(error{A}!S, s) else null; +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_6 = if (runtime_bool) @as(error{B}!S, s) else @as(error{A}!S, s); - _ = error_union_6 catch {}; - // ^^^^^^^^^^^^^ (error{B,A}!S)() +const error_union_6 = if (runtime_bool) @as(error{B}!S, s) else @as(error{A}!S, s); +// ^^^^^^^^^^^^^ (error{B,A}!S)() - const error_union_7 = if (runtime_bool) @as(error{A}!S, s) else @as(error{B}!S, s); - _ = error_union_7 catch {}; - // ^^^^^^^^^^^^^ (error{A,B}!S)() +const error_union_7 = if (runtime_bool) @as(error{A}!S, s) else @as(error{B}!S, s); +// ^^^^^^^^^^^^^ (error{A,B}!S)() - const error_union_8 = if (runtime_bool) @as(error{A}!?S, s) else @as(error{A}!S, s); - _ = error_union_8 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_8 = if (runtime_bool) @as(error{A}!?S, s) else @as(error{A}!S, s); +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_9 = if (runtime_bool) @as(error{A}!S, s) else @as(error{A}!?S, s); - _ = error_union_9 catch {}; - // ^^^^^^^^^^^^^ (error{A}!?S)() +const error_union_9 = if (runtime_bool) @as(error{A}!S, s) else @as(error{A}!?S, s); +// ^^^^^^^^^^^^^ (error{A}!?S)() - const error_union_10 = if (runtime_bool) @as(error{B}!?S, s) else @as(error{A}!S, s); - _ = error_union_10 catch {}; - // ^^^^^^^^^^^^^^ (error{B,A}!?S)() +const error_union_10 = if (runtime_bool) @as(error{B}!?S, s) else @as(error{A}!S, s); +// ^^^^^^^^^^^^^^ (error{B,A}!?S)() - const error_union_11 = if (runtime_bool) @as(error{A}!S, s) else @as(error{B}!?S, s); - _ = error_union_11 catch {}; - // ^^^^^^^^^^^^^^ (error{A,B}!?S)() +const error_union_11 = if (runtime_bool) @as(error{A}!S, s) else @as(error{B}!?S, s); +// ^^^^^^^^^^^^^^ (error{A,B}!?S)() - const error_union_12 = if (runtime_bool) @as(error{B}!error{A}!S, s) else @as(error{A}!?S, s); - _ = try try error_union_12; - // ^^^^^^^^^^^^^^ (error{B,A}!error{A}!?S)() +const error_union_12 = if (runtime_bool) @as(error{B}!error{A}!S, s) else @as(error{A}!?S, s); +// ^^^^^^^^^^^^^^ (error{B,A}!error{A}!?S)() - const error_union_13 = if (runtime_bool) @as(error{A}!?S, s) else @as(error{B}!error{A}!S, s); - _ = try try error_union_13; - // ^^^^^^^^^^^^^^ (error{A,B}!error{A}!?S)() +const error_union_13 = if (runtime_bool) @as(error{A}!?S, s) else @as(error{B}!error{A}!S, s); +// ^^^^^^^^^^^^^^ (error{A,B}!error{A}!?S)() - const error_union_14 = if (runtime_bool) @as(error{B}!error{A}!S, s) else @as(error{A}!error{B}!?S, s); - _ = try try error_union_14; - // ^^^^^^^^^^^^^^ (error{B,A}!error{A,B}!?S)() +const error_union_14 = if (runtime_bool) @as(error{B}!error{A}!S, s) else @as(error{A}!error{B}!?S, s); +// ^^^^^^^^^^^^^^ (error{B,A}!error{A,B}!?S)() - const error_union_15 = if (runtime_bool) @as(error{A}!error{B}!?S, s) else @as(error{B}!error{A}!S, s); - _ = try try error_union_15; - // ^^^^^^^^^^^^^^ (error{A,B}!error{B,A}!?S)() +const error_union_15 = if (runtime_bool) @as(error{A}!error{B}!?S, s) else @as(error{B}!error{A}!S, s); +// ^^^^^^^^^^^^^^ (error{A,B}!error{B,A}!?S)() - const error_union_16 = if (runtime_bool) error.A else s; - _ = error_union_16; - // ^^^^^^^^^^^^^^ (error{A}!S)() +const error_union_16 = if (runtime_bool) error.A else s; +// ^^^^^^^^^^^^^^ (error{A}!S)() - const error_union_17 = if (runtime_bool) s else error.A; - _ = error_union_17; - // ^^^^^^^^^^^^^^ (error{A}!S)() +const error_union_17 = if (runtime_bool) s else error.A; +// ^^^^^^^^^^^^^^ (error{A}!S)() - const error_union_18 = if (runtime_bool) error.A else @as(i32, 0); - _ = error_union_18; - // ^^^^^^^^^^^^^^ (error{A}!i32)() +const error_union_18 = if (runtime_bool) error.A else @as(i32, 0); +// ^^^^^^^^^^^^^^ (error{A}!i32)() - const error_union_19 = if (runtime_bool) @as(i32, 0) else error.A; - _ = error_union_19; - // ^^^^^^^^^^^^^^ (error{A}!i32)() +const error_union_19 = if (runtime_bool) @as(i32, 0) else error.A; +// ^^^^^^^^^^^^^^ (error{A}!i32)() - const error_union_20 = if (runtime_bool) error.A else @as(error{B}!S, s); - _ = error_union_20; - // ^^^^^^^^^^^^^^ (error{A,B}!S)() +const error_union_20 = if (runtime_bool) error.A else @as(error{B}!S, s); +// ^^^^^^^^^^^^^^ (error{A,B}!S)() - const error_union_21 = if (runtime_bool) @as(error{B}!S, s) else error.A; - _ = error_union_21; - // ^^^^^^^^^^^^^^ (error{A,B}!S)() +const error_union_21 = if (runtime_bool) @as(error{B}!S, s) else error.A; +// ^^^^^^^^^^^^^^ (error{A,B}!S)() - const error_union_22 = if (runtime_bool) error.A else @as(error{B}!i32, 0); - _ = error_union_22; - // ^^^^^^^^^^^^^^ (error{A,B}!i32)() +const error_union_22 = if (runtime_bool) error.A else @as(error{B}!i32, 0); +// ^^^^^^^^^^^^^^ (error{A,B}!i32)() - const error_union_23 = if (runtime_bool) @as(error{B}!i32, 0) else error.A; - _ = error_union_23; - // ^^^^^^^^^^^^^^ (error{A,B}!i32)() +const error_union_23 = if (runtime_bool) @as(error{B}!i32, 0) else error.A; +// ^^^^^^^^^^^^^^ (error{A,B}!i32)() +test "noreturn" { const noreturn_0 = if (runtime_bool) s else return; _ = noreturn_0; // ^^^^^^^^^^ (S)() @@ -169,11 +131,6 @@ pub fn main() !void { const noreturn_1 = if (runtime_bool) return else s; _ = noreturn_1; // ^^^^^^^^^^ (S)() - - // Use @compileLog to verify the expected type with the compiler: - // @compileLog(error_union_0); - - _ = &runtime_bool; } const comptime_bool: bool = true; From 532450cac357fb9676a30974c680c11a2ee1cfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 13 Jul 2025 21:01:20 -0700 Subject: [PATCH 3/8] Start reimplementation of resolvePeerTypes based on Sema --- src/analysis.zig | 502 ++++++++++++++++++------ tests/analysis/peer_type_resolution.zig | 55 ++- 2 files changed, 432 insertions(+), 125 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 4a5861aa6..df1d39a53 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1509,6 +1509,11 @@ fn resolvePeerTypes(analyser: *Analyser, a: Type, b: Type) error{OutOfMemory}!?T if (a.is_type_val or b.is_type_val) return null; if (a.eql(b)) return a; + var peer_tys = [_]?Type{ try a.typeOf(analyser), try b.typeOf(analyser) }; + if (try analyser.resolvePeerTypesInner(&peer_tys)) |ty| { + return ty.instanceTypeVal(analyser); + } + if (a.data == .ip_index and b.data == .ip_index) { const a_type = a.data.ip_index.type; const b_type = b.data.ip_index.type; @@ -1517,7 +1522,7 @@ fn resolvePeerTypes(analyser: *Analyser, a: Type, b: Type) error{OutOfMemory}!?T } } - return try analyser.resolvePeerTypesInternal(a, b) orelse try analyser.resolvePeerTypesInternal(b, a); + return null; } fn resolvePeerTypesIP(analyser: *Analyser, a: InternPool.Index, b: InternPool.Index) error{OutOfMemory}!?InternPool.Index { @@ -1539,123 +1544,6 @@ fn resolvePeerErrorSets(analyser: *Analyser, a: Type, b: Type) !?Type { return Type.fromIP(analyser, .type_type, resolved_index); } -fn resolvePeerTypesInternal(analyser: *Analyser, a: Type, b: Type) error{OutOfMemory}!?Type { - switch (a.data) { - .compile_error => return b, - .optional => |a_type| { - if (a_type.eql(try b.typeOf(analyser))) { - return a; - } - switch (b.data) { - .error_union => |b_info| { - if (a_type.eql(b_info.payload.*)) { - return .{ - .data = .{ - .error_union = .{ - .error_set = b_info.error_set, - .payload = try analyser.allocType(try a.typeOf(analyser)), - }, - }, - .is_type_val = false, - }; - } - }, - else => {}, - } - }, - .error_union => |a_info| { - if (a_info.payload.eql(try b.typeOf(analyser))) { - return a; - } - switch (b.data) { - .error_union => |b_info| { - const resolved_error_set = blk: { - const a_error_set = a_info.error_set orelse break :blk null; - const b_error_set = b_info.error_set orelse break :blk null; - if (a_error_set.eql(b_error_set.*)) break :blk a_error_set; - const resolved_error_set = try analyser.resolvePeerErrorSets(a_error_set.*, b_error_set.*) orelse break :blk null; - break :blk try analyser.allocType(resolved_error_set); - }; - const resolved_payload = blk: { - if (a_info.payload.eql(b_info.payload.*)) break :blk a_info.payload; - const a_instance = try a_info.payload.instanceTypeVal(analyser) orelse return null; - const b_instance = try b_info.payload.instanceTypeVal(analyser) orelse return null; - const resolved_instance = try analyser.resolvePeerTypes(a_instance, b_instance) orelse return null; - break :blk try analyser.allocType(try resolved_instance.typeOf(analyser)); - }; - return .{ - .data = .{ - .error_union = .{ - .error_set = resolved_error_set, - .payload = resolved_payload, - }, - }, - .is_type_val = false, - }; - }, - else => {}, - } - }, - .ip_index => |a_payload| switch (analyser.ip.zigTypeTag(a_payload.type) orelse return null) { - .noreturn => return b, - .null => switch (b.data) { - .optional => return b, - .error_union => |b_info| { - return .{ - .data = .{ - .error_union = .{ - .error_set = b_info.error_set, - .payload = try analyser.allocType(.{ - .data = .{ .optional = try analyser.allocType(b_info.payload.*) }, - .is_type_val = true, - }), - }, - }, - .is_type_val = false, - }; - }, - else => return .{ - .data = .{ .optional = try analyser.allocType(try b.typeOf(analyser)) }, - .is_type_val = false, - }, - }, - .error_set => switch (b.data) { - .error_union => |b_info| { - const resolved_error_set = blk: { - const a_error_set = try a.typeOf(analyser); - const b_error_set = b_info.error_set orelse break :blk null; - if (a_error_set.eql(b_error_set.*)) break :blk b_error_set; - const resolved_error_set = try analyser.resolvePeerErrorSets(a_error_set, b_error_set.*) orelse break :blk null; - break :blk try analyser.allocType(resolved_error_set); - }; - return .{ - .data = .{ - .error_union = .{ - .error_set = resolved_error_set, - .payload = b_info.payload, - }, - }, - .is_type_val = false, - }; - }, - else => return .{ - .data = .{ - .error_union = .{ - .error_set = try analyser.allocType(try a.typeOf(analyser)), - .payload = try analyser.allocType(try b.typeOf(analyser)), - }, - }, - .is_type_val = false, - }, - }, - else => {}, - }, - else => {}, - } - - return null; -} - fn resolveCallsiteReferences(analyser: *Analyser, decl_handle: DeclWithHandle) !?Type { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); @@ -3589,12 +3477,17 @@ pub const Type = struct { return entries[0].type; peer_type_resolution: { - var chosen = entries[0].type; - for (entries[1..]) |entry| { - const candidate = entry.type; - chosen = try analyser.resolvePeerTypes(chosen, candidate) orelse break :peer_type_resolution; + const peer_tys = try analyser.gpa.alloc(?Type, entries.len); + defer analyser.gpa.free(peer_tys); + + for (entries, peer_tys) |entry, *ty| { + if (entry.type.is_type_val) break :peer_type_resolution; + ty.* = try entry.type.typeOf(analyser); + } + + if (try analyser.resolvePeerTypesInner(peer_tys)) |ty| { + return try ty.instanceTypeVal(analyser); } - return chosen; } // Note that we don't hash/equate descriptors to remove @@ -4144,6 +4037,34 @@ pub const Type = struct { } } + fn zigTypeTag(ty: Type, analyser: *Analyser) ?std.builtin.TypeId { + if (!ty.is_type_val) return null; + return switch (ty.data) { + .either => null, + .anytype_parameter => null, + .optional => .optional, + .pointer => .pointer, + .array => .array, + .tuple => .@"struct", + .container => switch (ty.getContainerKind().?) { + .keyword_struct => .@"struct", + .keyword_enum => .@"enum", + .keyword_union => .@"union", + .keyword_opaque => .@"opaque", + else => null, + }, + .error_union => .error_union, + .function => .@"fn", + .union_tag => .@"enum", + .compile_error => .noreturn, + .type_parameter => null, + .ip_index => |payload| { + const ip_index = payload.index orelse return null; + return analyser.ip.zigTypeTag(ip_index); + }, + }; + } + pub fn stringifyTypeOf(ty: Type, analyser: *Analyser, options: FormatOptions) error{OutOfMemory}![]const u8 { const typeof = try ty.typeOf(analyser); var aw: std.Io.Writer.Allocating = .init(analyser.arena); @@ -6384,3 +6305,338 @@ pub const ReferencedType = struct { } }; }; + +// Based on src/Sema.zig from the zig codebase +// https://github.com/ziglang/zig/blob/master/src/Sema.zig +fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { + const PeerResolveStrategy = enum { + unknown, + error_set, + error_union, + nullable, + optional, + array, + vector, + c_ptr, + ptr, + func, + enum_or_union, + int_or_float, + tuple, + exact, + + const PeerResolveStrategy = @This(); + + fn merge(a: PeerResolveStrategy, b: PeerResolveStrategy, reason_peer: *usize, b_peer_idx: usize) PeerResolveStrategy { + const s0_is_a = @intFromEnum(a) <= @intFromEnum(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, + }; + + const reason_method: ReasonMethod, const strat: PeerResolveStrategy = switch (s0) { + .unknown => .{ .all_s1, s1 }, + .error_set => switch (s1) { + .error_set => .{ .either, .error_set }, + else => .{ .all_s0, .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 => .{ .all_s0, .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 }, + }, + .enum_or_union => switch (s1) { + .enum_or_union => .{ .either, .enum_or_union }, + else => .{ .all_s0, .enum_or_union }, + }, + .int_or_float => switch (s1) { + .int_or_float => .{ .either, .int_or_float }, + else => .{ .all_s1, s1 }, + }, + .tuple => switch (s1) { + .exact => .{ .all_s1, .exact }, + else => .{ .all_s0, .tuple }, + }, + .exact => .{ .all_s0, .exact }, + }; + + switch (reason_method) { + .all_s0 => { + if (!s0_is_a) { + reason_peer.* = b_peer_idx; + } + }, + .all_s1 => { + if (s0_is_a) { + reason_peer.* = b_peer_idx; + } + }, + .either => { + reason_peer.* = @min(reason_peer.*, b_peer_idx); + }, + } + + return strat; + } + + fn select(ty: Type, tag: std.builtin.TypeId) PeerResolveStrategy { + return switch (tag) { + .type, .void, .bool, .@"opaque", .frame, .@"anyframe" => .exact, + .noreturn, .undefined => .unknown, + .null => .nullable, + .comptime_int, .int, .comptime_float, .float => .int_or_float, + .pointer => if (ty.data == .pointer and ty.data.pointer.size == .c) .c_ptr else .ptr, + .array => .array, + .vector => .vector, + .optional => .optional, + .error_set => .error_set, + .error_union => .error_union, + .enum_literal, .@"enum", .@"union" => .enum_or_union, + .@"struct" => if (ty.data == .tuple) .tuple else .exact, + .@"fn" => .func, + }; + } + }; + + var strat_reason: usize = 0; + var s: PeerResolveStrategy = .unknown; + for (peer_tys, 0..) |opt_ty, i| { + const ty = opt_ty orelse continue; + const tag = ty.zigTypeTag(analyser) orelse return null; + s = s.merge(PeerResolveStrategy.select(ty, tag), &strat_reason, i); + } + + if (s == .unknown) { + s = .exact; + } else { + for (peer_tys) |*ty_ptr| { + const ty = ty_ptr.* orelse continue; + switch (ty.zigTypeTag(analyser).?) { + .noreturn, .undefined => ty_ptr.* = null, + else => {}, + } + } + } + + switch (s) { + .unknown => unreachable, + + .error_set => { + var final_set: ?Type = null; + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + if (final_set) |cur_set| { + final_set = try analyser.resolvePeerErrorSets(cur_set, ty) orelse { + return null; + }; + } else { + final_set = ty; + } + } + return final_set.?; + }, + + .error_union => { + var final_set: union(enum) { + unknown: void, + inferred: void, + known: Type, + } = .unknown; + for (peer_tys) |*ty_ptr| { + const ty = ty_ptr.* orelse continue; + const set_ty = switch (ty.zigTypeTag(analyser).?) { + .error_set => blk: { + ty_ptr.* = null; + break :blk ty; + }, + .error_union => blk: { + ty_ptr.* = ty.data.error_union.payload.*; + const set_ty = ty.data.error_union.error_set orelse { + final_set = .inferred; + continue; + }; + break :blk set_ty.*; + }, + else => continue, + }; + const merged_set = switch (final_set) { + .known => |cur_set| blk: { + break :blk try analyser.resolvePeerErrorSets(cur_set, set_ty) orelse { + return null; + }; + }, + .inferred => continue, + .unknown => set_ty, + }; + final_set = .{ .known = merged_set }; + } + std.debug.assert(final_set != .unknown); + const final_payload = try analyser.resolvePeerTypesInner(peer_tys) orelse { + return null; + }; + return .{ + .data = .{ + .error_union = .{ + .error_set = switch (final_set) { + .unknown => unreachable, + .inferred => null, + .known => |set_ty| try analyser.allocType(set_ty), + }, + .payload = try analyser.allocType(final_payload), + }, + }, + .is_type_val = true, + }; + }, + + .nullable => { + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag(analyser).?) { + .null => {}, + else => return null, + } + } + return Type.fromIP(analyser, .type_type, .null_type); + }, + + .optional => { + for (peer_tys) |*ty_ptr| { + const ty = ty_ptr.* orelse continue; + switch (ty.zigTypeTag(analyser).?) { + .null => ty_ptr.* = null, + .optional => ty_ptr.* = ty.data.optional.*, + else => {}, + } + } + const child_ty = try analyser.resolvePeerTypesInner(peer_tys) orelse { + return null; + }; + return .{ + .data = .{ .optional = try analyser.allocType(child_ty) }, + .is_type_val = true, + }; + }, + + .array => return null, // TODO + + .vector => return null, // TODO + + .c_ptr => return null, // TODO + + .ptr => return null, // TODO + + .func => return null, // TODO + + .enum_or_union => return null, // TODO + + .int_or_float => { + var ip_indices: std.ArrayListUnmanaged(InternPool.Index) = try .initCapacity(analyser.gpa, peer_tys.len); + defer ip_indices.deinit(analyser.gpa); + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + const ip_index = switch (ty.data) { + .ip_index => |payload| payload.index orelse return null, + else => return null, + }; + ip_indices.appendAssumeCapacity(ip_index); + } + const ip_index = try analyser.ip.resolvePeerTypes(analyser.gpa, ip_indices.items, builtin.target); + if (ip_index == .none) return null; + return Type.fromIP(analyser, .type_type, ip_index); + }, + + .tuple => { + var field_count: ?usize = null; + + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + + if (ty.data != .tuple) { + return null; + } + + if (field_count) |count| { + if (ty.data.tuple.len != count) { + return null; + } + } else { + field_count = ty.data.tuple.len; + } + } + + std.debug.assert(field_count != null); + + const field_types = try analyser.arena.alloc(Type, field_count.?); + const sub_peer_tys = try analyser.arena.alloc(?Type, peer_tys.len); + + for (field_types, 0..) |*field_ty, field_index| { + for (peer_tys, sub_peer_tys) |opt_ty, *peer_field_ty| { + const ty = opt_ty orelse { + peer_field_ty.* = null; + continue; + }; + peer_field_ty.* = ty.data.tuple[field_index]; + } + + field_ty.* = try analyser.resolvePeerTypesInner(sub_peer_tys) orelse { + return null; + }; + } + + return .{ + .data = .{ .tuple = field_types }, + .is_type_val = true, + }; + }, + + .exact => { + var expect_ty: ?Type = null; + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + if (expect_ty) |expect| { + if (!ty.eql(expect)) { + return null; + } + } else { + expect_ty = ty; + } + } + return expect_ty.?; + }, + } +} diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index 4be8ab221..3390d00a4 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -15,6 +15,30 @@ const widened_int_0 = if (runtime_bool) @as(i8, 0) else @as(i16, 0); const widened_int_1 = if (runtime_bool) @as(i16, 0) else @as(i8, 0); // ^^^^^^^^^^^^^ (i16)() +const u8_and_comptime_int = if (runtime_bool) @as(u8, 0) else 0; +// ^^^^^^^^^^^^^^^^^^^ (u8)() + +const comptime_int_and_u8 = if (runtime_bool) 0 else @as(u8, 0); +// ^^^^^^^^^^^^^^^^^^^ (u8)() + +const widened_float_0 = if (runtime_bool) @as(f16, 0) else @as(f32, 0); +// ^^^^^^^^^^^^^^^ (f32)() + +const widened_float_1 = if (runtime_bool) @as(f32, 0) else @as(f16, 0); +// ^^^^^^^^^^^^^^^ (f32)() + +const f64_and_comptime_int = if (runtime_bool) @as(f64, 0) else 0; +// ^^^^^^^^^^^^^^^^^^^^ (f64)() + +const comptime_int_and_f64 = if (runtime_bool) 0 else @as(f64, 0); +// ^^^^^^^^^^^^^^^^^^^^ (f64)() + +const f64_and_comptime_float = if (runtime_bool) @as(f64, 0) else 0.1; +// ^^^^^^^^^^^^^^^^^^^^^^ (f64)() + +const comptime_float_and_f64 = if (runtime_bool) 0.1 else @as(f64, 0); +// ^^^^^^^^^^^^^^^^^^^^^^ (f64)() + const optional_0 = if (runtime_bool) s else @as(?S, s); // ^^^^^^^^^^ (?S)() @@ -115,13 +139,25 @@ const error_union_20 = if (runtime_bool) error.A else @as(error{B}!S, s); // ^^^^^^^^^^^^^^ (error{A,B}!S)() const error_union_21 = if (runtime_bool) @as(error{B}!S, s) else error.A; -// ^^^^^^^^^^^^^^ (error{A,B}!S)() +// ^^^^^^^^^^^^^^ (error{B,A}!S)() const error_union_22 = if (runtime_bool) error.A else @as(error{B}!i32, 0); // ^^^^^^^^^^^^^^ (error{A,B}!i32)() const error_union_23 = if (runtime_bool) @as(error{B}!i32, 0) else error.A; -// ^^^^^^^^^^^^^^ (error{A,B}!i32)() +// ^^^^^^^^^^^^^^ (error{B,A}!i32)() + +const error_union_24 = if (runtime_bool) @as(error{A}!?S, null) else s; +// ^^^^^^^^^^^^^^ (error{A}!?S)() + +const error_union_25 = if (runtime_bool) s else @as(error{A}!?S, null); +// ^^^^^^^^^^^^^^ (error{A}!?S)() + +const error_union_26 = if (runtime_bool) @as(error{A}!i8, 0) else @as(i16, 0); +// ^^^^^^^^^^^^^^ (error{A}!i16)() + +const error_union_27 = if (runtime_bool) @as(i16, 0) else @as(error{A}!i8, 0); +// ^^^^^^^^^^^^^^ (error{A}!i16)() test "noreturn" { const noreturn_0 = if (runtime_bool) s else return; @@ -138,6 +174,21 @@ const comptime_bool: bool = true; const comptime_int_and_void = if (comptime_bool) 0 else {}; // ^^^^^^^^^^^^^^^^^^^^^ (either type)() +const optional_comptime_int = if (comptime_bool) @as(?comptime_int, 0) else 0; +// ^^^^^^^^^^^^^^^^^^^^^ (?comptime_int)() + +const optional_comptime_float = if (comptime_bool) @as(?comptime_float, 0) else 0; +// ^^^^^^^^^^^^^^^^^^^^^^^ (?comptime_float)() + +const null_error_union = if (comptime_bool) @as(error{A}!@TypeOf(null), null) else null; +// ^^^^^^^^^^^^^^^^ (error{A}!@TypeOf(null))() + +const f32_and_u32 = if (comptime_bool) @as(f32, 0) else @as(i32, 0); +// ^^^^^^^^^^^ (either type)() + +const u32_and_f32 = if (comptime_bool) @as(u32, 0) else @as(f32, 0); +// ^^^^^^^^^^^ (either type)() + const compile_error_0 = if (comptime_bool) s else @compileError("Foo"); // ^^^^^^^^^^^^^^^ (S)() From ece585dffc28b8e289a9014d2bb081a4e8c25ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:12:21 -0700 Subject: [PATCH 4/8] Resolve peer array/tuple types --- src/analysis.zig | 116 +++++++++++++++++++++++- tests/analysis/peer_type_resolution.zig | 12 +++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index df1d39a53..cc3ddf006 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -6552,7 +6552,78 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { }; }, - .array => return null, // TODO + .array => { + var len: ?u64 = null; + var sentinel: InternPool.Index = .none; + var elem_ty: ?Type = null; + + for (peer_tys) |*ty_ptr| { + const ty = ty_ptr.* orelse continue; + + if (ty.data != .array) { + const arr_like = analyser.typeIsArrayLike(ty) orelse { + return null; + }; + + if (len) |cur_len| { + if (arr_like.len != cur_len) return null; + } else { + len = arr_like.len; + } + + sentinel = .none; + + continue; + } + + const arr_info = ty.data.array; + const arr_len = arr_info.elem_count orelse { + return null; + }; + + const cur_elem_ty = elem_ty orelse { + if (len) |cur_len| { + if (arr_len != cur_len) return null; + } else { + len = arr_len; + sentinel = arr_info.sentinel; + } + elem_ty = arr_info.elem_ty.*; + continue; + }; + + if (arr_info.elem_count != len) { + return null; + } + + const peer_elem_ty = arr_info.elem_ty.*; + if (!peer_elem_ty.eql(cur_elem_ty)) { + // TODO: check if coercible + return null; + } + + if (sentinel != .none) { + if (arr_info.sentinel != .none) { + if (arr_info.sentinel != sentinel) sentinel = .none; + } else { + sentinel = .none; + } + } + } + + std.debug.assert(elem_ty != null); + + return .{ + .data = .{ + .array = .{ + .elem_count = len, + .sentinel = sentinel, + .elem_ty = try analyser.allocType(elem_ty.?), + }, + }, + .is_type_val = true, + }; + }, .vector => return null, // TODO @@ -6640,3 +6711,46 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { }, } } + +const ArrayLike = struct { + len: u64, + /// `noreturn` indicates that this is `.{}` so can coerce to anything + elem_ty: Type, +}; +fn typeIsArrayLike(analyser: *Analyser, ty: Type) ?ArrayLike { + std.debug.assert(ty.is_type_val); + const ip_index = switch (ty.data) { + .ip_index => |payload| payload.index orelse return null, + .array => |info| return .{ + .len = info.elem_count orelse return null, + .elem_ty = info.elem_ty.*, + }, + .tuple => |field_tys| { + const elem_ty = field_tys[0]; + for (field_tys[1..]) |field_ty| { + if (!field_ty.eql(elem_ty)) { + return null; + } + } + return .{ + .len = field_tys.len, + .elem_ty = elem_ty, + }; + }, + else => return null, + }; + if (ip_index == .empty_struct_type) { + return .{ + .len = 0, + .elem_ty = Type.fromIP(analyser, .type_type, .noreturn_type), + }; + } + return switch (analyser.ip.indexToKey(ip_index)) { + .array_type => |info| .{ + .len = info.len, + .elem_ty = Type.fromIP(analyser, .type_type, info.child), + }, + .tuple_type => null, // TODO + else => null, + }; +} diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index 3390d00a4..924ddd0e9 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -189,6 +189,18 @@ const f32_and_u32 = if (comptime_bool) @as(f32, 0) else @as(i32, 0); const u32_and_f32 = if (comptime_bool) @as(u32, 0) else @as(f32, 0); // ^^^^^^^^^^^ (either type)() +const array_2_and_array_3 = if (comptime_bool) [2]S{ s, s } else [3]S{ s, s, s }; +// ^^^^^^^^^^^^^^^^^^^ (either type)() + +const tuple_2_and_array_3 = if (comptime_bool) @as(struct { S, S }, .{ s, s }) else [3]S{ s, s, s }; +// ^^^^^^^^^^^^^^^^^^^ (either type)() + +const array_3_and_tuple_2 = if (comptime_bool) [3]S{ s, s, s } else @as(struct { S, S }, .{ s, s }); +// ^^^^^^^^^^^^^^^^^^^ (either type)() + +const array_3_and_tuple_3 = if (comptime_bool) [3]S{ s, s, s } else @as(struct { S, S, S }, .{ s, s, s }); +// ^^^^^^^^^^^^^^^^^^^ ([3]S)() + const compile_error_0 = if (comptime_bool) s else @compileError("Foo"); // ^^^^^^^^^^^^^^^ (S)() From f79b31c730703be8035215b559af2cd3a1ca324a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:59:50 -0700 Subject: [PATCH 5/8] Resolve peer C pointer types --- src/analysis.zig | 122 ++++++++++++++++++++++++++++++++++++- tests/analysis/pointer.zig | 22 +++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index cc3ddf006..ca12bc623 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -3137,6 +3137,7 @@ pub const Type = struct { const b_type = b.pointer; if (a_type.size != b_type.size) return false; if (a_type.sentinel != b_type.sentinel) return false; + if (a_type.is_const != b_type.is_const) return false; if (!a_type.elem_ty.eql(b_type.elem_ty.*)) return false; }, .array => |a_type| { @@ -6627,7 +6628,64 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { .vector => return null, // TODO - .c_ptr => return null, // TODO + .c_ptr => { + var opt_ptr_info: ?PointerInfo = null; + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag(analyser).?) { + .comptime_int => continue, + .int => { + const ptr_bits = builtin.target.ptrBitWidth(); + const bits = analyser.ip.intInfo(ty.data.ip_index.index.?, builtin.target).bits; + if (bits >= ptr_bits) continue; + }, + .null => continue, + else => {}, + } + + if (!analyser.typeIsPointerAtRuntime(ty)) { + return null; + } + + const peer_info = analyser.typePointerInfo(ty).?; + + var ptr_info = opt_ptr_info orelse { + opt_ptr_info = peer_info; + continue; + }; + + if (!ptr_info.elem_ty.eql(peer_info.elem_ty)) { + // TODO: coerce C pointer types + return null; + } + + if (ptr_info.flags.alignment != ptr_info.flags.alignment) { + // TODO: find minimum C pointer alignment + return null; + } + + if (ptr_info.flags.address_space != peer_info.flags.address_space) { + return null; + } + + ptr_info.flags.is_const = ptr_info.flags.is_const or peer_info.flags.is_const; + ptr_info.flags.is_volatile = ptr_info.flags.is_volatile or peer_info.flags.is_volatile; + + opt_ptr_info = ptr_info; + } + const info = opt_ptr_info.?; + return .{ + .data = .{ + .pointer = .{ + .elem_ty = try analyser.allocType(info.elem_ty), + .sentinel = .none, + .size = .c, + .is_const = info.flags.is_const, + }, + }, + .is_type_val = true, + }; + }, .ptr => return null, // TODO @@ -6754,3 +6812,65 @@ fn typeIsArrayLike(analyser: *Analyser, ty: Type) ?ArrayLike { else => null, }; } + +const PointerInfo = struct { + is_optional: bool, + elem_ty: Type, + sentinel: InternPool.Index, + flags: InternPool.Key.Pointer.Flags, +}; +fn typePointerInfo(analyser: *Analyser, ty: Type) ?PointerInfo { + std.debug.assert(ty.is_type_val); + const ip_index = switch (ty.data) { + .ip_index => |payload| payload.index orelse return null, + .pointer => |p| return .{ + .is_optional = false, + .elem_ty = p.elem_ty.*, + .sentinel = p.sentinel, + .flags = .{ + .size = p.size, + .is_const = p.is_const, + }, + }, + .optional => |child| switch (child.data) { + .pointer => |p| return .{ + .is_optional = true, + .elem_ty = p.elem_ty.*, + .sentinel = p.sentinel, + .flags = .{ + .size = p.size, + .is_const = p.is_const, + }, + }, + else => return null, + }, + else => return null, + }; + return switch (analyser.ip.indexToKey(ip_index)) { + .pointer_type => |p| .{ + .is_optional = false, + .elem_ty = Type.fromIP(analyser, .type_type, p.elem_type), + .sentinel = p.sentinel, + .flags = p.flags, + }, + .optional_type => |info| switch (analyser.ip.indexToKey(info.payload_type)) { + .pointer_type => |p| .{ + .is_optional = true, + .elem_ty = Type.fromIP(analyser, .type_type, p.elem_type), + .sentinel = p.sentinel, + .flags = p.flags, + }, + else => null, + }, + else => null, + }; +} + +fn typeIsPointerAtRuntime(analyser: *Analyser, ty: Type) bool { + const ptr_info = analyser.typePointerInfo(ty) orelse return false; + return switch (ptr_info.flags.size) { + .slice => false, + .c => !ptr_info.is_optional, + .one, .many => !ptr_info.is_optional or !ptr_info.flags.is_allowzero, + }; +} diff --git a/tests/analysis/pointer.zig b/tests/analysis/pointer.zig index 494633e9e..a965a57da 100644 --- a/tests/analysis/pointer.zig +++ b/tests/analysis/pointer.zig @@ -65,6 +65,13 @@ const one_minus_slice = one_u32 - slice_u32; const one_minus_c = one_u32 - c_u32; // ^^^^^^^^^^^ (usize)() +// +// array pointer *[n]T +// + +const array_u32: *const [2]u32 = &[_]u32{ 1, 2 }; +// ^^^^^^^^^ (*const [2]u32)() + // // many item pointer [*]T // @@ -234,6 +241,21 @@ const c_minus_slice = c_u32 - slice_u32; const c_minus_c = c_u32 - c_u32; // ^^^^^^^^^ (usize)() +const c_u32_or_one_u32 = if (runtime_bool) c_u32 else one_u32; +// ^^^^^^^^^^^^^^^^ ([*c]const u32)() + +const c_u32_or_array_u32 = if (runtime_bool) c_u32 else array_u32; +// ^^^^^^^^^^^^^^^^^^ (either type)() + +const c_u32_or_many_u32 = if (runtime_bool) c_u32 else many_u32; +// ^^^^^^^^^^^^^^^^^ ([*c]const u32)() + +const c_u32_or_slice_u32 = if (runtime_bool) c_u32 else slice_u32; +// ^^^^^^^^^^^^^^^^^^ (either type)() + +const c_u32_or_c_u32 = if (runtime_bool) c_u32 else c_u32; +// ^^^^^^^^^^^^^^ ([*c]const u32)() + var runtime_index: usize = 5; var runtime_u8: u8 = 1; var runtime_i8: i8 = -1; From 705f1e67b53f0e94a68d118aa30b013d9bbc9f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:13:26 -0700 Subject: [PATCH 6/8] Resolve peer pointer types --- src/analysis.zig | 280 +++++++++++++++++++++++- tests/analysis/peer_type_resolution.zig | 3 + tests/analysis/pointer.zig | 114 ++++++++++ 3 files changed, 396 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index ca12bc623..6040c6d48 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -6687,7 +6687,285 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { }; }, - .ptr => return null, // TODO + .ptr => { + var any_slice = false; + var any_abi_aligned = false; + var opt_ptr_info: ?PointerInfo = null; + + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + const peer_info: PointerInfo = switch (ty.zigTypeTag(analyser).?) { + .pointer => analyser.typePointerInfo(ty).?, + .@"fn" => .{ + .is_optional = false, + .elem_ty = ty, + .sentinel = .none, + .flags = .{ .size = .one }, + }, + else => return null, + }; + + switch (peer_info.flags.size) { + .one, .many => {}, + .slice => any_slice = true, + .c => return null, + } + + var ptr_info = opt_ptr_info orelse { + opt_ptr_info = peer_info; + continue; + }; + + if (peer_info.flags.alignment == 0) { + any_abi_aligned = true; + } else if (ptr_info.flags.alignment == 0) { + any_abi_aligned = true; + ptr_info.flags.alignment = peer_info.flags.alignment; + } else { + ptr_info.flags.alignment = @min(ptr_info.flags.alignment, peer_info.flags.alignment); + } + + if (ptr_info.flags.address_space != peer_info.flags.address_space) { + return null; + } + + ptr_info.flags.is_const = ptr_info.flags.is_const or peer_info.flags.is_const; + ptr_info.flags.is_volatile = ptr_info.flags.is_volatile or peer_info.flags.is_volatile; + ptr_info.flags.is_allowzero = ptr_info.flags.is_allowzero or peer_info.flags.is_allowzero; + + const peer_sentinel: InternPool.Index = switch (peer_info.flags.size) { + .one => switch (peer_info.elem_ty.data) { + .array => |array_info| array_info.sentinel, + .ip_index => |payload| switch (analyser.ip.indexToKey(payload.index orelse .unknown_type)) { + .array_type => |info| info.sentinel, + else => .none, + }, + else => .none, + }, + .many, .slice => peer_info.sentinel, + .c => unreachable, + }; + + const cur_sentinel: InternPool.Index = switch (ptr_info.flags.size) { + .one => switch (ptr_info.elem_ty.data) { + .array => |array_info| array_info.sentinel, + .ip_index => |payload| switch (analyser.ip.indexToKey(payload.index orelse .unknown_type)) { + .array_type => |info| info.sentinel, + else => .none, + }, + else => .none, + }, + .many, .slice => ptr_info.sentinel, + .c => unreachable, + }; + + const peer_pointee_array = analyser.typeIsArrayLike(peer_info.elem_ty); + const cur_pointee_array = analyser.typeIsArrayLike(ptr_info.elem_ty); + + good: { + switch (peer_info.flags.size) { + .one => switch (ptr_info.flags.size) { + .one => { + if (ptr_info.elem_ty.eql(peer_info.elem_ty)) { + break :good; + } + // TODO: coerce pointer types + + const cur_arr = cur_pointee_array orelse return null; + const peer_arr = peer_pointee_array orelse return null; + + if (cur_arr.elem_ty.eql(peer_arr.elem_ty)) { + const elem_ty = peer_arr.elem_ty; + // *[n:x]T + *[n:y]T = *[n]T + if (cur_arr.len == peer_arr.len) { + ptr_info.elem_ty = .{ + .data = .{ + .array = .{ + .elem_count = cur_arr.len, + .sentinel = .none, + .elem_ty = try analyser.allocType(elem_ty), + }, + }, + .is_type_val = true, + }; + break :good; + } + // *[a]T + *[b]T = []T + ptr_info.flags.size = .slice; + ptr_info.elem_ty = elem_ty; + break :good; + } + // TODO: coerce array types + + if (peer_arr.elem_ty.isNoreturnType()) { + // *struct{} + *[a]T = []T + ptr_info.flags.size = .slice; + ptr_info.elem_ty = cur_arr.elem_ty; + break :good; + } + + if (cur_arr.elem_ty.isNoreturnType()) { + // *[a]T + *struct{} = []T + ptr_info.flags.size = .slice; + ptr_info.elem_ty = peer_arr.elem_ty; + break :good; + } + + return null; + }, + .many => { + // Only works for *[n]T + [*]T -> [*]T + const arr = peer_pointee_array orelse return null; + if (ptr_info.elem_ty.eql(arr.elem_ty)) { + break :good; + } + // TODO: coerce array and many-item pointer types + return null; + }, + .slice => { + // Only works for *[n]T + []T -> []T + const arr = peer_pointee_array orelse return null; + if (ptr_info.elem_ty.eql(arr.elem_ty)) { + break :good; + } + // TODO: coerce array and slice types + if (arr.elem_ty.isNoreturnType()) { + // *struct{} + []T -> []T + break :good; + } + return null; + }, + .c => unreachable, + }, + .many => switch (ptr_info.flags.size) { + .one => { + // Only works for [*]T + *[n]T -> [*]T + const arr = cur_pointee_array orelse return null; + if (arr.elem_ty.eql(peer_info.elem_ty)) { + ptr_info.flags.size = .many; + ptr_info.elem_ty = peer_info.elem_ty; + break :good; + } + // TODO: coerce many-item pointer and array types + return null; + }, + .many => { + if (ptr_info.elem_ty.eql(peer_info.elem_ty)) { + break :good; + } + // TODO: coerce many-item pointer types + return null; + }, + .slice => { + // Only works if no peers are actually slices + if (any_slice) { + return null; + } + // Okay, then works for [*]T + "[]T" -> [*]T + if (ptr_info.elem_ty.eql(peer_info.elem_ty)) { + ptr_info.flags.size = .many; + break :good; + } + // TODO: coerce many-item pointer and "slice" types + return null; + }, + .c => unreachable, + }, + .slice => switch (ptr_info.flags.size) { + .one => { + // Only works for []T + *[n]T -> []T + const arr = cur_pointee_array orelse return null; + if (arr.elem_ty.eql(peer_info.elem_ty)) { + ptr_info.flags.size = .slice; + ptr_info.elem_ty = peer_info.elem_ty; + break :good; + } + // TODO: coerce slice and array types + if (arr.elem_ty.isNoreturnType()) { + // []T + *struct{} -> []T + ptr_info.flags.size = .slice; + ptr_info.elem_ty = peer_info.elem_ty; + break :good; + } + return null; + }, + .many => { + return null; + }, + .slice => { + if (ptr_info.elem_ty.eql(peer_info.elem_ty)) { + break :good; + } + // TODO: coerce slice types + return null; + }, + .c => unreachable, + }, + .c => unreachable, + } + } + + sentinel: { + no_sentinel: { + if (peer_sentinel == .none) break :no_sentinel; + if (cur_sentinel == .none) break :no_sentinel; + if (peer_sentinel != cur_sentinel) { + // TODO: coerce pointer sentinels + return null; + } + break :sentinel; + } + ptr_info.sentinel = .none; + if (ptr_info.flags.size == .one) switch (ptr_info.elem_ty.data) { + .array => |*array_info| array_info.sentinel = .none, + .ip_index => |*payload| switch (analyser.ip.indexToKey(payload.index orelse .unknown_type)) { + .array_type => |info| { + payload.index = try analyser.ip.get(analyser.gpa, .{ .array_type = .{ + .len = info.len, + .child = info.child, + .sentinel = .none, + } }); + }, + else => {}, + }, + else => {}, + }; + } + + opt_ptr_info = ptr_info; + } + + const info = opt_ptr_info.?; + const pointee = info.elem_ty; + if (pointee.isNoreturnType()) { + return null; + } + switch (pointee.data) { + .array => |array_info| { + if (array_info.elem_ty.isNoreturnType()) { + return null; + } + }, + else => {}, + } + + if (any_abi_aligned and info.flags.alignment != 0) { + // TODO: find minimum pointer alignment + return null; + } + + return .{ + .data = .{ + .pointer = .{ + .elem_ty = try analyser.allocType(info.elem_ty), + .sentinel = info.sentinel, + .size = info.flags.size, + .is_const = info.flags.is_const, + }, + }, + .is_type_val = true, + }; + }, .func => return null, // TODO diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index 924ddd0e9..1502e83c2 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -1,3 +1,6 @@ +// Also see: +// - pointer.zig + const S = struct { int: i64, float: f32, diff --git a/tests/analysis/pointer.zig b/tests/analysis/pointer.zig index a965a57da..418ca2e19 100644 --- a/tests/analysis/pointer.zig +++ b/tests/analysis/pointer.zig @@ -65,6 +65,30 @@ const one_minus_slice = one_u32 - slice_u32; const one_minus_c = one_u32 - c_u32; // ^^^^^^^^^^^ (usize)() +const one_u32_or_one_u32 = if (runtime_bool) one_u32 else one_u32; +// ^^^^^^^^^^^^^^^^^^ (*const u32)() + +const one_u32_or_array_u32 = if (runtime_bool) one_u32 else array_u32; +// ^^^^^^^^^^^^^^^^^^^^ (either type)() + +const one_u32_or_many_u32 = if (runtime_bool) one_u32 else many_u32; +// ^^^^^^^^^^^^^^^^^^^ (either type)() + +const one_u32_or_slice_u32 = if (runtime_bool) one_u32 else slice_u32; +// ^^^^^^^^^^^^^^^^^^^^ (either type)() + +const one_u32_or_c_u32 = if (runtime_bool) one_u32 else c_u32; +// ^^^^^^^^^^^^^^^^ ([*c]const u32)() + +const one_u32_or_runtime_one_u32 = if (runtime_bool) one_u32 else runtime_one_u32; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^ (*const u32)() + +const one_u32_or_empty = if (runtime_bool) one_u32 else &.{}; +// ^^^^^^^^^^^^^^^^ (either type)() + +const empty_or_one_u32 = if (runtime_bool) &.{} else one_u32; +// ^^^^^^^^^^^^^^^^ (either type)() + // // array pointer *[n]T // @@ -72,6 +96,30 @@ const one_minus_c = one_u32 - c_u32; const array_u32: *const [2]u32 = &[_]u32{ 1, 2 }; // ^^^^^^^^^ (*const [2]u32)() +const array_u32_or_one_u32 = if (runtime_bool) array_u32 else one_u32; +// ^^^^^^^^^^^^^^^^^^^^ (either type)() + +const array_u32_or_array_u32 = if (runtime_bool) array_u32 else array_u32; +// ^^^^^^^^^^^^^^^^^^^^^^ (*const [2]u32)() + +const array_u32_or_many_u32 = if (runtime_bool) array_u32 else many_u32; +// ^^^^^^^^^^^^^^^^^^^^^ ([*]const u32)() + +const array_u32_or_slice_u32 = if (runtime_bool) array_u32 else slice_u32; +// ^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const array_u32_or_c_u32 = if (runtime_bool) array_u32 else c_u32; +// ^^^^^^^^^^^^^^^^^^ (either type)() + +const array_u32_or_runtime_array_u32 = if (runtime_bool) array_u32 else runtime_array_u32; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (*const [2]u32)() + +const array_u32_or_empty = if (runtime_bool) array_u32 else &.{}; +// ^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const empty_or_array_u32 = if (runtime_bool) &.{} else array_u32; +// ^^^^^^^^^^^^^^^^^^ ([]const u32)() + // // many item pointer [*]T // @@ -129,6 +177,30 @@ const many_minus_slice = many_u32 - slice_u32; const many_minus_c = many_u32 - c_u32; // ^^^^^^^^^^^^ (usize)() +const many_u32_or_one_u32 = if (runtime_bool) many_u32 else one_u32; +// ^^^^^^^^^^^^^^^^^^^ (either type)() + +const many_u32_or_array_u32 = if (runtime_bool) many_u32 else array_u32; +// ^^^^^^^^^^^^^^^^^^^^^ ([*]const u32)() + +const many_u32_or_many_u32 = if (runtime_bool) many_u32 else many_u32; +// ^^^^^^^^^^^^^^^^^^^^ ([*]const u32)() + +const many_u32_or_slice_u32 = if (runtime_bool) many_u32 else slice_u32; +// ^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const many_u32_or_c_u32 = if (runtime_bool) many_u32 else c_u32; +// ^^^^^^^^^^^^^^^^^ ([*c]const u32)() + +const many_u32_or_runtime_many_u32 = if (runtime_bool) many_u32 else runtime_many_u32; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([*]const u32)() + +const many_u32_or_empty = if (runtime_bool) many_u32 else &.{}; +// ^^^^^^^^^^^^^^^^^ (either type)() + +const empty_or_many_u32 = if (runtime_bool) &.{} else many_u32; +// ^^^^^^^^^^^^^^^^^ (either type)() + // // slice []T // @@ -184,6 +256,30 @@ const slice_minus_slice = slice_u32 - slice_u32; const slice_minus_c = slice_u32 - c_u32; // ^^^^^^^^^^^^^ (unknown)() +const slice_u32_or_one_u32 = if (runtime_bool) slice_u32 else one_u32; +// ^^^^^^^^^^^^^^^^^^^^ (either type)() + +const slice_u32_or_array_u32 = if (runtime_bool) slice_u32 else array_u32; +// ^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const slice_u32_or_many_u32 = if (runtime_bool) slice_u32 else many_u32; +// ^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const slice_u32_or_slice_u32 = if (runtime_bool) slice_u32 else slice_u32; +// ^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const slice_u32_or_c_u32 = if (runtime_bool) slice_u32 else c_u32; +// ^^^^^^^^^^^^^^^^^^ (either type)() + +const slice_u32_or_runtime_slice_u32 = if (runtime_bool) slice_u32 else runtime_slice_u32; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const slice_u32_or_empty = if (runtime_bool) slice_u32 else &.{}; +// ^^^^^^^^^^^^^^^^^^ ([]const u32)() + +const empty_or_slice_u32 = if (runtime_bool) &.{} else slice_u32; +// ^^^^^^^^^^^^^^^^^^ ([]const u32)() + // // C pointer [*c]T // @@ -256,9 +352,27 @@ const c_u32_or_slice_u32 = if (runtime_bool) c_u32 else slice_u32; const c_u32_or_c_u32 = if (runtime_bool) c_u32 else c_u32; // ^^^^^^^^^^^^^^ ([*c]const u32)() +const c_u32_or_runtime_c_u32 = if (runtime_bool) c_u32 else runtime_c_u32; +// ^^^^^^^^^^^^^^^^^^^^^^ ([*c]const u32)() + +const c_u32_or_empty = if (runtime_bool) c_u32 else &.{}; +// ^^^^^^^^^^^^^^ (either type)() + +const empty_or_c_u32 = if (runtime_bool) &.{} else c_u32; +// ^^^^^^^^^^^^^^ (either type)() + var runtime_index: usize = 5; var runtime_u8: u8 = 1; var runtime_i8: i8 = -1; +var runtime_bool = true; +var runtime_u32: u32 = 0; +var runtime_u32_array: [2]u32 = .{ 1, 2 }; + +const runtime_one_u32: *u32 = &runtime_u32; +const runtime_c_u32: [*c]u32 = &runtime_u32; +const runtime_array_u32: *[2]u32 = &runtime_u32_array; +const runtime_many_u32: [*]u32 = &runtime_u32_array; +const runtime_slice_u32: []u32 = &runtime_u32_array; comptime { // Use @compileLog to verify the expected type with the compiler: From 49d69424996ef9d272549e3562df7aac75f8afe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:55:21 -0700 Subject: [PATCH 7/8] Resolve peer function types --- src/analysis.zig | 25 +++++++++++++++---- tests/analysis/peer_type_resolution.zig | 32 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 6040c6d48..102b518cf 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2279,6 +2279,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, options: ResolveOptions) error var buf: [1]Ast.Node.Index = undefined; const fn_proto = tree.fullFnProto(&buf, node).?; + // TODO: should we avoid calling innermostContainer if this is a function type? const container_type = options.container_type orelse try analyser.innermostContainer(handle, tree.tokenStart(fn_proto.ast.fn_token)); const doc_comments = try getDocComments(analyser.arena, tree, node); const name = if (fn_proto.name_token) |t| tree.tokenSlice(t) else null; @@ -3097,8 +3098,6 @@ pub const Type = struct { } }, .function => |info| { - std.hash.autoHash(hasher, info.fn_token); - hasher.update(info.handle.uri); info.container_type.hashWithHasher(hasher); for (info.parameters) |param| { param.type.hashWithHasher(hasher); @@ -3178,8 +3177,6 @@ pub const Type = struct { }, .function => |a_info| { const b_info = b.function; - if (a_info.fn_token != b_info.fn_token) return false; - if (!std.mem.eql(u8, a_info.handle.uri, b_info.handle.uri)) return false; if (!a_info.container_type.eql(b_info.container_type.*)) return false; if (a_info.parameters.len != b_info.parameters.len) return false; for (a_info.parameters, b_info.parameters) |a_param, b_param| { @@ -6967,7 +6964,25 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { }; }, - .func => return null, // TODO + .func => { + var opt_cur_ty: ?Type = null; + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + const cur_ty = opt_cur_ty orelse { + opt_cur_ty = ty; + continue; + }; + if (ty.zigTypeTag(analyser).? != .@"fn") { + return null; + } + if (cur_ty.eql(ty)) { + continue; + } + // TODO: coerce function types + return null; + } + return opt_cur_ty.?; + }, .enum_or_union => return null, // TODO diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index 1502e83c2..752eabe0c 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -162,6 +162,33 @@ const error_union_26 = if (runtime_bool) @as(error{A}!i8, 0) else @as(i16, 0); const error_union_27 = if (runtime_bool) @as(i16, 0) else @as(error{A}!i8, 0); // ^^^^^^^^^^^^^^ (error{A}!i16)() +const FnCoerce = struct { + fn errorsA() [2]error{A} { + return .{ error.A, error.A }; + } + fn errorsAB() [2]error{ A, B } { + return .{ error.A, error.A }; + } + fn pointers() [2]*const S { + return .{ &s, &s }; + } + fn optionalPointers() [2]?*const S { + return .{ &s, null }; + } +}; + +const fn_coerce_0 = if (runtime_bool) &FnCoerce.errorsA else &FnCoerce.errorsAB; +// ^^^^^^^^^^^ (either type)() TODO this should be `*const fn () [2]error{A,B}` + +const fn_coerce_1 = if (runtime_bool) &FnCoerce.errorsAB else &FnCoerce.errorsA; +// ^^^^^^^^^^^ (either type)() TODO this should be `*const fn () [2]error{A,B}` + +const fn_coerce_2 = if (runtime_bool) &FnCoerce.pointers else &FnCoerce.optionalPointers; +// ^^^^^^^^^^^ (either type)() TODO this should be `*const fn () [2]?*const S` + +const fn_coerce_3 = if (runtime_bool) &FnCoerce.optionalPointers else &FnCoerce.pointers; +// ^^^^^^^^^^^ (either type)() TODO this should be `*const fn () [2]?*const S` + test "noreturn" { const noreturn_0 = if (runtime_bool) s else return; _ = noreturn_0; @@ -186,6 +213,11 @@ const optional_comptime_float = if (comptime_bool) @as(?comptime_float, 0) else const null_error_union = if (comptime_bool) @as(error{A}!@TypeOf(null), null) else null; // ^^^^^^^^^^^^^^^^ (error{A}!@TypeOf(null))() +fn void_fn() void {} + +const optional_fn = if (comptime_bool) @as(?fn () void, void_fn) else void_fn; +// ^^^^^^^^^^^ (?fn () void)() + const f32_and_u32 = if (comptime_bool) @as(f32, 0) else @as(i32, 0); // ^^^^^^^^^^^ (either type)() From 1efd3f0c31ca4126a7038131e452eb5b162b02ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Wed, 16 Jul 2025 00:38:51 -0700 Subject: [PATCH 8/8] Resolve peer enum/union types --- src/analysis.zig | 69 ++++++++++++++++++++-- tests/analysis/peer_type_resolution.zig | 76 +++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 6 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 102b518cf..5184f5024 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -982,6 +982,13 @@ pub fn resolveUnwrapErrorUnionType(analyser: *Analyser, ty: Type, side: ErrorUni } fn resolveUnionTag(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type { + const tag_type = try analyser.resolveUnionTagType(ty) orelse + return null; + + return try tag_type.instanceTypeVal(analyser); +} + +fn resolveUnionTagType(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type { if (!ty.is_type_val) return null; @@ -1000,12 +1007,10 @@ fn resolveUnionTag(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type { return null; if (container_decl.ast.enum_token != null) - return .{ .data = .{ .union_tag = try analyser.allocType(ty) }, .is_type_val = false }; + return .{ .data = .{ .union_tag = try analyser.allocType(ty) }, .is_type_val = true }; - if (container_decl.ast.arg.unwrap()) |arg| { - const tag_type = (try analyser.resolveTypeOfNode(.of(arg, handle))) orelse return null; - return try tag_type.instanceTypeVal(analyser) orelse return null; - } + if (container_decl.ast.arg.unwrap()) |arg| + return try analyser.resolveTypeOfNode(.of(arg, handle)); return null; } @@ -6984,7 +6989,59 @@ fn resolvePeerTypesInner(analyser: *Analyser, peer_tys: []?Type) !?Type { return opt_cur_ty.?; }, - .enum_or_union => return null, // TODO + .enum_or_union => { + var opt_cur_ty: ?Type = null; + + for (peer_tys) |opt_ty| { + const ty = opt_ty orelse continue; + switch (ty.zigTypeTag(analyser).?) { + .enum_literal, .@"enum", .@"union" => {}, + else => return null, + } + const cur_ty = opt_cur_ty orelse { + opt_cur_ty = ty; + continue; + }; + + if ((cur_ty.isUnionType() and !cur_ty.isTaggedUnion()) or + (ty.isUnionType() and !ty.isTaggedUnion())) + { + if (cur_ty.eql(ty)) continue; + return null; + } + + switch (cur_ty.zigTypeTag(analyser).?) { + .enum_literal => { + opt_cur_ty = ty; + }, + .@"enum" => switch (ty.zigTypeTag(analyser).?) { + .enum_literal => {}, + .@"enum" => { + if (!ty.eql(cur_ty)) return null; + }, + .@"union" => { + const tag_ty = try analyser.resolveUnionTagType(ty); + if (!tag_ty.?.eql(cur_ty)) return null; + opt_cur_ty = ty; + }, + else => unreachable, + }, + .@"union" => switch (ty.zigTypeTag(analyser).?) { + .enum_literal => {}, + .@"enum" => { + const cur_tag_ty = try analyser.resolveUnionTagType(cur_ty); + if (!ty.eql(cur_tag_ty.?)) return null; + }, + .@"union" => { + if (!ty.eql(cur_ty)) return null; + }, + else => unreachable, + }, + else => unreachable, + } + } + return opt_cur_ty.?; + }, .int_or_float => { var ip_indices: std.ArrayListUnmanaged(InternPool.Index) = try .initCapacity(analyser.gpa, peer_tys.len); diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig index 752eabe0c..0602be92a 100644 --- a/tests/analysis/peer_type_resolution.zig +++ b/tests/analysis/peer_type_resolution.zig @@ -5,11 +5,36 @@ const S = struct { int: i64, float: f32, }; + const s: S = .{ .int = 0, .float = 1.2, }; +const E = enum { + foo, + bar, + baz, +}; + +const e: E = .bar; + +const T = union(E) { + foo: void, + bar: void, + baz: u16, +}; + +const t: T = .{ .baz = 3 }; + +const U = union { + foo: void, + bar: void, + baz: void, +}; + +const u: U = .{ .baz = {} }; + var runtime_bool: bool = true; const widened_int_0 = if (runtime_bool) @as(i8, 0) else @as(i16, 0); @@ -218,6 +243,57 @@ fn void_fn() void {} const optional_fn = if (comptime_bool) @as(?fn () void, void_fn) else void_fn; // ^^^^^^^^^^^ (?fn () void)() +const optional_enum_literal = if (comptime_bool) @as(?@Type(.enum_literal), .foo) else .bar; +// ^^^^^^^^^^^^^^^^^^^^^ (?@Type(.enum_literal))() + +const enum_literal_and_enum_literal = if (comptime_bool) .foo else .bar; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (@Type(.enum_literal))() + +const enum_literal_and_enum = if (comptime_bool) .foo else e; +// ^^^^^^^^^^^^^^^^^^^^^ (E)() + +const enum_literal_and_union = if (comptime_bool) .foo else u; +// ^^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const enum_literal_and_tagged_union = if (comptime_bool) .foo else t; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (T)() + +const enum_and_enum_literal = if (comptime_bool) e else .foo; +// ^^^^^^^^^^^^^^^^^^^^^ (E)() + +const enum_and_enum = if (comptime_bool) e else e; +// ^^^^^^^^^^^^^ (E)() + +const enum_and_union = if (comptime_bool) e else u; +// ^^^^^^^^^^^^^^ (either type)() + +const enum_and_tagged_union = if (comptime_bool) e else t; +// ^^^^^^^^^^^^^^^^^^^^^ (T)() + +const union_and_enum_literal = if (comptime_bool) u else .foo; +// ^^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const union_and_enum = if (comptime_bool) u else e; +// ^^^^^^^^^^^^^^ (either type)() + +const union_and_union = if (comptime_bool) u else u; +// ^^^^^^^^^^^^^^^ (U)() + +const union_and_tagged_union = if (comptime_bool) u else t; +// ^^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const tagged_union_and_enum_literal = if (comptime_bool) t else .foo; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (T)() + +const tagged_union_and_enum = if (comptime_bool) t else e; +// ^^^^^^^^^^^^^^^^^^^^^ (T)() + +const tagged_union_and_union = if (comptime_bool) t else u; +// ^^^^^^^^^^^^^^^^^^^^^^ (either type)() + +const tagged_union_and_tagged_union = if (comptime_bool) t else t; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (T)() + const f32_and_u32 = if (comptime_bool) @as(f32, 0) else @as(i32, 0); // ^^^^^^^^^^^ (either type)()