From 207822062499f2aca33d54ba27f224b12dafccf1 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, 23 Feb 2025 08:28:23 -0800 Subject: [PATCH] resolve peer types of optionals and interned values (#2193) --- src/analysis.zig | 70 +++++++++++++++++++++++-- tests/analysis/peer_type_resolution.zig | 44 ++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 tests/analysis/peer_type_resolution.zig diff --git a/src/analysis.zig b/src/analysis.zig index 610126dea..5d9b6ff11 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -7,6 +7,7 @@ //! - `lookupSymbolContainer` //! +const builtin = @import("builtin"); const std = @import("std"); const DocumentStore = @import("DocumentStore.zig"); const Ast = std.zig.Ast; @@ -2118,7 +2119,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e if (try analyser.resolveTypeOfNodeInternal(.{ .handle = handle, .node = if_node.ast.else_expr })) |t| either.appendAssumeCapacity(.{ .type = t, .descriptor = try std.fmt.allocPrint(analyser.arena.allocator(), "!({s})", .{offsets.nodeToSlice(tree, if_node.ast.cond_expr)}) }); - return Type.fromEither(analyser.arena.allocator(), either.constSlice()); + return Type.fromEither(analyser, either.constSlice()); }, .@"switch", .switch_comma, @@ -2144,7 +2145,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e }); } - return Type.fromEither(analyser.arena.allocator(), either.items); + return Type.fromEither(analyser, either.items); }, .@"while", .while_simple, @@ -2722,13 +2723,23 @@ pub const Type = struct { descriptor: []const u8, }; - pub fn fromEither(arena: std.mem.Allocator, entries: []const TypeWithDescriptor) error{OutOfMemory}!?Type { + pub fn fromEither(analyser: *Analyser, entries: []const TypeWithDescriptor) error{OutOfMemory}!?Type { + const arena = analyser.arena.allocator(); if (entries.len == 0) return null; if (entries.len == 1) return entries[0].type; + peer_type_resolution: { + var chosen = entries[0].type; + for (entries[1..]) |entry| { + const candidate = entry.type; + chosen = try resolvePeerTypes(analyser, chosen, candidate) orelse break :peer_type_resolution; + } + return chosen; + } + // Note that we don't hash/equate descriptors to remove // duplicates @@ -2774,6 +2785,57 @@ pub const Type = struct { }; } + fn resolvePeerTypes(analyser: *Analyser, a: Type, b: Type) error{OutOfMemory}!?Type { + if (a.is_type_val or b.is_type_val) return null; + if (a.eql(b)) return a; + + if (a.data == .ip_index and b.data == .ip_index) { + const types = [_]InternPool.Index{ a.data.ip_index.type, b.data.ip_index.type }; + const resolved_type = try analyser.ip.resolvePeerTypes(analyser.gpa, &types, builtin.target); + return fromIP(analyser, resolved_type, null); + } + + switch (a.data) { + .optional => |a_type| { + if (a_type.eql(b.typeOf(analyser))) { + return a; + } + }, + .ip_index => |a_payload| switch (a_payload.type) { + .null_type => switch (b.data) { + .optional => return b, + else => return .{ + .data = .{ .optional = try analyser.allocType(b.typeOf(analyser)) }, + .is_type_val = false, + }, + }, + else => {}, + }, + else => {}, + } + + switch (b.data) { + .optional => |b_type| { + if (b_type.eql(a.typeOf(analyser))) { + return b; + } + }, + .ip_index => |b_payload| switch (b_payload.type) { + .null_type => switch (a.data) { + .optional => return a, + else => return .{ + .data = .{ .optional = try analyser.allocType(a.typeOf(analyser)) }, + .is_type_val = false, + }, + }, + else => {}, + }, + else => {}, + } + + return null; + } + /// Resolves possible types of a type (single for all except either) /// Drops duplicates pub fn getAllTypesWithHandles(ty: Type, arena: std.mem.Allocator) ![]const Type { @@ -4153,7 +4215,7 @@ pub const DeclWithHandle = struct { }); } - const maybe_type = try Type.fromEither(analyser.arena.allocator(), possible.items); + const maybe_type = try Type.fromEither(analyser, possible.items); if (maybe_type) |ty| analyser.resolved_callsites.getPtr(pay).?.* = ty; break :blk maybe_type; } diff --git a/tests/analysis/peer_type_resolution.zig b/tests/analysis/peer_type_resolution.zig new file mode 100644 index 000000000..dee04c9b5 --- /dev/null +++ b/tests/analysis/peer_type_resolution.zig @@ -0,0 +1,44 @@ +const S = struct { + int: i64, + float: f32, +}; + +pub fn main() void { + var runtime_bool: bool = undefined; + + const widened_int_0 = if (runtime_bool) @as(i8, undefined) else @as(i16, undefined); + // ^^^^^^^^^^^^^ (i16)() + + const widened_int_1 = if (runtime_bool) @as(i16, undefined) else @as(i8, undefined); + // ^^^^^^^^^^^^^ (i16)() + + const optional_0 = if (runtime_bool) @as(S, undefined) else @as(?S, undefined); + // ^^^^^^^^^^ (?S)() + + const optional_1 = if (runtime_bool) @as(?S, undefined) else @as(S, undefined); + // ^^^^^^^^^^ (?S)() + + const optional_2 = if (runtime_bool) null else @as(S, undefined); + // ^^^^^^^^^^ (?S)() + + const optional_3 = if (runtime_bool) @as(S, undefined) else null; + // ^^^^^^^^^^ (?S)() + + const optional_4 = if (runtime_bool) null else @as(?S, undefined); + // ^^^^^^^^^^ (?S)() + + const optional_5 = if (runtime_bool) @as(?S, undefined) else null; + // ^^^^^^^^^^ (?S)() + + // Use @compileLog to verify the expected type with the compiler: + @compileLog(widened_int_0); + @compileLog(widened_int_1); + @compileLog(optional_0); + @compileLog(optional_1); + @compileLog(optional_2); + @compileLog(optional_3); + @compileLog(optional_4); + @compileLog(optional_5); + + runtime_bool = undefined; +}