Skip to content

Commit d8be34f

Browse files
Expand conditional types in hover popup
1 parent e31807f commit d8be34f

File tree

4 files changed

+394
-14
lines changed

4 files changed

+394
-14
lines changed

src/analysis.zig

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,7 +1751,7 @@ fn resolveCallsiteReferences(analyser: *Analyser, decl_handle: DeclWithHandle) !
17511751

17521752
if (real_param_idx >= call.ast.params.len) continue;
17531753

1754-
const ty = resolve_ty: {
1754+
var ty = resolve_ty: {
17551755
// don't resolve callsite references while resolving callsite references
17561756
const old_collect_callsite_references = analyser.collect_callsite_references;
17571757
defer analyser.collect_callsite_references = old_collect_callsite_references;
@@ -1767,6 +1767,9 @@ fn resolveCallsiteReferences(analyser: *Analyser, decl_handle: DeclWithHandle) !
17671767
)) orelse continue;
17681768
};
17691769

1770+
ty = try ty.typeOf(analyser);
1771+
std.debug.assert(ty.is_type_val);
1772+
17701773
const loc = offsets.tokenToPosition(tree, tree.nodeMainToken(call.ast.params[real_param_idx]), .@"utf-8");
17711774
try possible.append(analyser.arena, .{
17721775
.type = ty,
@@ -3681,27 +3684,211 @@ pub const Type = struct {
36813684
};
36823685
}
36833686

3684-
/// Resolves possible types of a type (single for all except either)
3687+
/// Resolves all possible types by recursively expanding any conditional types.
36853688
/// Drops duplicates
36863689
pub fn getAllTypesWithHandles(ty: Type, analyser: *Analyser) error{OutOfMemory}![]const Type {
36873690
var all_types: ArraySet = .empty;
36883691
try ty.getAllTypesWithHandlesArraySet(analyser, &all_types);
36893692
return all_types.keys();
36903693
}
36913694

3695+
fn isConditional(ty: Type) bool {
3696+
return switch (ty.data) {
3697+
.either => true,
3698+
.anytype_parameter => true,
3699+
.optional => |child_ty| child_ty.isConditional(),
3700+
.pointer => |info| info.elem_ty.isConditional(),
3701+
.array => |info| info.elem_ty.isConditional(),
3702+
.tuple => |types| {
3703+
for (types) |t|
3704+
if (t.isConditional()) return true;
3705+
return false;
3706+
},
3707+
.container => |info| {
3708+
for (info.bound_params.values()) |t|
3709+
if (t.isConditional()) return true;
3710+
return false;
3711+
},
3712+
.error_union => |info| {
3713+
if (info.payload.isConditional()) return true;
3714+
if (info.error_set) |e|
3715+
if (e.isConditional()) return true;
3716+
return false;
3717+
},
3718+
.function => |info| {
3719+
if (info.container_type.isConditional()) return true;
3720+
if (info.return_value.isConditional()) return true;
3721+
for (info.parameters) |param|
3722+
if (param.type.isConditional()) return true;
3723+
return false;
3724+
},
3725+
.union_tag,
3726+
.for_range,
3727+
.compile_error,
3728+
.type_parameter,
3729+
.ip_index,
3730+
=> false,
3731+
};
3732+
}
3733+
3734+
// is infinite recursion possible here?
36923735
pub fn getAllTypesWithHandlesArraySet(ty: Type, analyser: *Analyser, all_types: *ArraySet) !void {
36933736
const arena = analyser.arena;
3737+
if (!ty.isConditional()) {
3738+
try all_types.put(arena, ty, {});
3739+
return;
3740+
}
36943741
switch (ty.data) {
3742+
.union_tag,
3743+
.for_range,
3744+
.compile_error,
3745+
.type_parameter,
3746+
.ip_index,
3747+
=> unreachable,
36953748
.either => |entries| {
36963749
for (entries) |entry| {
36973750
const entry_ty: Type = .{ .data = entry.type_data, .is_type_val = ty.is_type_val };
36983751
try entry_ty.getAllTypesWithHandlesArraySet(analyser, all_types);
36993752
}
37003753
},
3701-
else => try all_types.put(arena, ty, {}),
3754+
.anytype_parameter => |info| {
3755+
if (info.type_from_callsite_references) |t| {
3756+
try t.getAllTypesWithHandlesArraySet(analyser, all_types);
3757+
} else {
3758+
try all_types.put(arena, ty, {});
3759+
}
3760+
},
3761+
.optional => |child_ty| {
3762+
for (try child_ty.getAllTypesWithHandles(analyser)) |t| {
3763+
const new_child_ty = try analyser.allocType(t);
3764+
try all_types.put(arena, .{ .data = .{ .optional = new_child_ty }, .is_type_val = ty.is_type_val }, {});
3765+
}
3766+
},
3767+
inline .pointer, .array => |info, tag| {
3768+
for (try info.elem_ty.getAllTypesWithHandles(analyser)) |t| {
3769+
var new_info = info;
3770+
new_info.elem_ty = try analyser.allocType(t);
3771+
const data = @unionInit(Type.Data, @tagName(tag), new_info);
3772+
try all_types.put(arena, .{ .data = data, .is_type_val = ty.is_type_val }, {});
3773+
}
3774+
},
3775+
.tuple => |types| {
3776+
var possible_types: ArrayMap([]const Type) = .empty;
3777+
for (types) |t| {
3778+
try possible_types.put(arena, t, try t.getAllTypesWithHandles(analyser));
3779+
}
3780+
var iter: ComboIterator = try .init(arena, &possible_types);
3781+
while (iter.next()) |combo| {
3782+
const new_types = try arena.alloc(Type, types.len);
3783+
for (new_types, types) |*new, old| new.* = combo.get(old).?;
3784+
try all_types.put(arena, .{ .data = .{ .tuple = new_types }, .is_type_val = ty.is_type_val }, {});
3785+
}
3786+
},
3787+
.container => |info| {
3788+
var possible_types: ArrayMap([]const Type) = .empty;
3789+
const types = info.bound_params.values();
3790+
for (types) |t| {
3791+
try possible_types.put(arena, t, try t.getAllTypesWithHandles(analyser));
3792+
}
3793+
var iter: ComboIterator = try .init(arena, &possible_types);
3794+
while (iter.next()) |combo| {
3795+
const new_types = try arena.alloc(Type, types.len);
3796+
for (new_types, types) |*new, old| new.* = combo.get(old).?;
3797+
var new_info = info;
3798+
new_info.bound_params = try .init(arena, info.bound_params.keys(), new_types);
3799+
try all_types.put(arena, .{ .data = .{ .container = new_info }, .is_type_val = ty.is_type_val }, {});
3800+
}
3801+
},
3802+
.error_union => |info| {
3803+
var possible_types: ArrayMap([]const Type) = .empty;
3804+
try possible_types.put(arena, info.payload.*, try info.payload.getAllTypesWithHandles(analyser));
3805+
if (info.error_set) |t| {
3806+
try possible_types.put(arena, t.*, try t.getAllTypesWithHandles(analyser));
3807+
}
3808+
var iter: ComboIterator = try .init(arena, &possible_types);
3809+
while (iter.next()) |combo| {
3810+
var new_info = info;
3811+
new_info.payload = try analyser.allocType(combo.get(info.payload.*).?);
3812+
if (info.error_set) |t| {
3813+
new_info.error_set = try analyser.allocType(combo.get(t.*).?);
3814+
}
3815+
try all_types.put(arena, .{ .data = .{ .error_union = new_info }, .is_type_val = ty.is_type_val }, {});
3816+
}
3817+
},
3818+
.function => |info| {
3819+
var possible_types: ArrayMap([]const Type) = .empty;
3820+
try possible_types.put(arena, info.container_type.*, try info.container_type.getAllTypesWithHandles(analyser));
3821+
for (info.parameters) |param| {
3822+
try possible_types.put(arena, param.type, try param.type.getAllTypesWithHandles(analyser));
3823+
}
3824+
if (info.return_value.is_type_val) {
3825+
try possible_types.put(arena, info.return_value.*, try info.return_value.getAllTypesWithHandles(analyser));
3826+
} else {
3827+
const return_type = try info.return_value.typeOf(analyser);
3828+
try possible_types.put(arena, return_type, try return_type.getAllTypesWithHandles(analyser));
3829+
}
3830+
var iter: ComboIterator = try .init(arena, &possible_types);
3831+
while (iter.next()) |combo| {
3832+
var new_info = info;
3833+
new_info.container_type = try analyser.allocType(combo.get(info.container_type.*).?);
3834+
new_info.parameters = try arena.alloc(Data.Parameter, info.parameters.len);
3835+
@memcpy(new_info.parameters, info.parameters);
3836+
for (new_info.parameters, info.parameters) |*new, old| {
3837+
new.type = combo.get(old.type).?;
3838+
}
3839+
if (info.return_value.is_type_val) {
3840+
new_info.return_value = try analyser.allocType(combo.get(info.return_value.*).?);
3841+
} else {
3842+
const return_type = try info.return_value.typeOf(analyser);
3843+
const return_value = try combo.get(return_type).?.instanceTypeVal(analyser);
3844+
new_info.return_value = try analyser.allocType(return_value.?);
3845+
}
3846+
try all_types.put(arena, .{ .data = .{ .function = new_info }, .is_type_val = ty.is_type_val }, {});
3847+
}
3848+
},
37023849
}
37033850
}
37043851

3852+
const ComboIterator = struct {
3853+
possible_types: *const ArrayMap([]const Type),
3854+
current_combo: ArrayMap(Type),
3855+
total_combos: usize,
3856+
counter: usize,
3857+
3858+
fn init(
3859+
arena: std.mem.Allocator,
3860+
possible_types: *const ArrayMap([]const Type),
3861+
) !ComboIterator {
3862+
var current_combo: ArrayMap(Type) = .empty;
3863+
try current_combo.entries.resize(arena, possible_types.count());
3864+
@memcpy(current_combo.keys(), possible_types.keys());
3865+
try current_combo.reIndex(arena);
3866+
3867+
var total_combos: usize = 1;
3868+
for (possible_types.values()) |types| {
3869+
total_combos *= types.len;
3870+
}
3871+
3872+
return .{
3873+
.possible_types = possible_types,
3874+
.current_combo = current_combo,
3875+
.total_combos = total_combos,
3876+
.counter = 0,
3877+
};
3878+
}
3879+
3880+
fn next(iter: *ComboIterator) ?*const ArrayMap(Type) {
3881+
if (iter.counter == iter.total_combos) return null;
3882+
var x = iter.counter;
3883+
for (iter.current_combo.values(), iter.possible_types.values()) |*t, types| {
3884+
t.* = types[x % types.len];
3885+
x /= types.len;
3886+
}
3887+
iter.counter += 1;
3888+
return &iter.current_combo;
3889+
}
3890+
};
3891+
37053892
pub fn instanceTypeVal(self: Type, analyser: *Analyser) error{OutOfMemory}!?Type {
37063893
if (!self.is_type_val) return null;
37073894
return switch (self.data) {
@@ -4185,7 +4372,13 @@ pub const Type = struct {
41854372
});
41864373
},
41874374
.either => try writer.writeAll("either type"), // TODO
4188-
.compile_error => |node_handle| try writer.writeAll(offsets.nodeToSlice(node_handle.handle.tree, node_handle.node)),
4375+
.compile_error => |node_handle| {
4376+
if (options.truncate_container_decls) {
4377+
try writer.writeAll("@compileError(...)");
4378+
} else {
4379+
try writer.writeAll(offsets.nodeToSlice(node_handle.handle.tree, node_handle.node));
4380+
}
4381+
},
41894382
.type_parameter => |token_handle| {
41904383
const token = token_handle.token;
41914384
const handle = token_handle.handle;

src/features/hover.zig

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,22 +100,26 @@ fn hoverSymbolRecursive(
100100
};
101101

102102
var referenced: Analyser.ReferencedType.Set = .empty;
103-
var resolved_type_str: []const u8 = "unknown";
103+
var resolved_type_strings: std.ArrayListUnmanaged([]const u8) = .empty;
104104
if (try decl_handle.resolveType(analyser)) |resolved_type| {
105105
if (try resolved_type.docComments(arena)) |doc|
106106
try doc_strings.append(arena, doc);
107-
resolved_type_str = try std.fmt.allocPrint(arena, "{}", .{try resolved_type.fmtTypeOf(analyser, .{
108-
.referenced = &referenced,
109-
.truncate_container_decls = false,
110-
})});
107+
const typeof = try resolved_type.typeOf(analyser);
108+
const possible_types = try typeof.getAllTypesWithHandles(analyser);
109+
for (possible_types) |ty| {
110+
try resolved_type_strings.append(arena, try std.fmt.allocPrint(arena, "{}", .{ty.fmtTypeVal(analyser, .{
111+
.referenced = &referenced,
112+
.truncate_container_decls = possible_types.len > 1,
113+
})}));
114+
}
111115
}
112116
const referenced_types: []const Analyser.ReferencedType = referenced.keys();
113117
return try hoverSymbolResolved(
114118
arena,
115119
markup_kind,
116120
doc_strings.items,
117121
def_str,
118-
resolved_type_str,
122+
resolved_type_strings.items,
119123
referenced_types,
120124
);
121125
}
@@ -125,13 +129,17 @@ fn hoverSymbolResolved(
125129
markup_kind: types.MarkupKind,
126130
doc_strings: []const []const u8,
127131
def_str: []const u8,
128-
resolved_type_str: []const u8,
132+
resolved_type_strings: []const []const u8,
129133
referenced_types: []const Analyser.ReferencedType,
130134
) error{OutOfMemory}![]const u8 {
131135
var hover_text: std.ArrayListUnmanaged(u8) = .empty;
132136
const writer = hover_text.writer(arena);
133137
if (markup_kind == .markdown) {
134-
try writer.print("```zig\n{s}\n```\n```zig\n({s})\n```", .{ def_str, resolved_type_str });
138+
try writer.print("```zig\n{s}\n```", .{def_str});
139+
for (resolved_type_strings) |resolved_type_str|
140+
try writer.print("\n```zig\n({s})\n```", .{resolved_type_str});
141+
if (resolved_type_strings.len == 0)
142+
try writer.writeAll("\n```zig\n(unknown)\n```");
135143
if (referenced_types.len > 0)
136144
try writer.print("\n\n" ++ "Go to ", .{});
137145
for (referenced_types, 0..) |ref, index| {
@@ -142,7 +150,11 @@ fn hoverSymbolResolved(
142150
try writer.print("[{s}]({s}#L{d})", .{ ref.str, ref.handle.uri, line });
143151
}
144152
} else {
145-
try writer.print("{s}\n({s})", .{ def_str, resolved_type_str });
153+
try writer.print("{s}", .{def_str});
154+
for (resolved_type_strings) |resolved_type_str|
155+
try writer.print("\n({s})", .{resolved_type_str});
156+
if (resolved_type_strings.len == 0)
157+
try writer.writeAll("\n(unknown)");
146158
}
147159

148160
if (doc_strings.len > 0) {
@@ -276,7 +288,7 @@ fn hoverDefinitionGlobal(
276288
if (std.mem.eql(u8, name, "_")) return null;
277289
if (try analyser.resolvePrimitive(name)) |ip_index| {
278290
const resolved_type_str = try std.fmt.allocPrint(arena, "{}", .{analyser.ip.typeOf(ip_index).fmt(analyser.ip)});
279-
break :blk try hoverSymbolResolved(arena, markup_kind, &.{}, name, resolved_type_str, &.{});
291+
break :blk try hoverSymbolResolved(arena, markup_kind, &.{}, name, &.{resolved_type_str}, &.{});
280292
}
281293
}
282294
const decl = (try analyser.lookupSymbolGlobal(handle, name, pos_index)) orelse return null;

tests/analysis/either.zig

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const StructU32 = struct {
2+
field: u32 = 42,
3+
fn function(self: @This()) @This() {
4+
return self;
5+
}
6+
};
7+
8+
const StructF64 = struct {
9+
field: f64 = 3.14,
10+
fn function(self: @This()) @This() {
11+
return self;
12+
}
13+
};
14+
15+
fn GenericStruct(T: type) type {
16+
return struct { field: T };
17+
}
18+
19+
const condition = true;
20+
21+
const EitherType = if (condition) StructU32 else StructF64;
22+
// ^^^^^^^^^^ (type)()
23+
24+
const EitherError = if (condition) error{Foo} else error{Bar};
25+
// ^^^^^^^^^^^ (type)()
26+
27+
const either: EitherType = .{};
28+
// ^^^^^^ (either type)()
29+
30+
// TODO this should be `either type`
31+
const field = either.field;
32+
// ^^^^^ (u32)()
33+
34+
const pointer = &either;
35+
// ^^^^^^^ (*const either type)()
36+
37+
const array = [_]EitherType{either};
38+
// ^^^^^ ([1]either type)()
39+
40+
const tuple: struct { EitherType } = .{either};
41+
// ^^^^^ (struct { either type })()
42+
43+
const optional: ?EitherType = null;
44+
// ^^^^^^^^ (?either type)()
45+
46+
const error_union: EitherError!EitherType = error.Foo;
47+
// ^^^^^^^^^^^ (either type!either type)()
48+
49+
const generic: GenericStruct(EitherType) = .{ .field = either };
50+
// ^^^^^^^ (GenericStruct(either type))()
51+
52+
// TODO this should be `either type`
53+
const function0 = EitherType.function;
54+
// ^^^^^^^^^ (fn (StructU32) StructU32)()
55+
56+
fn function1() EitherType {}
57+
// ^^^^^^^^^ (fn () either type)()
58+
59+
fn function2(_: EitherType) void {}
60+
// ^^^^^^^^^ (fn (either type) void)()
61+
62+
fn function3(_: EitherType) EitherType {}
63+
// ^^^^^^^^^ (fn (either type) either type)()

0 commit comments

Comments
 (0)