Skip to content

Expand conditional types in hover popup #2349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
319 changes: 282 additions & 37 deletions src/analysis.zig

Large diffs are not rendered by default.

59 changes: 29 additions & 30 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn typeToCompletion(builder: *Builder, ty: Analyser.Type) error{OutOfMemory}!voi
.detail = try std.fmt.allocPrint(
builder.arena,
"{}",
.{ty.fmt(builder.analyser, .{ .truncate_container_decls = false })},
.{try ty.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = false })},
),
});
return;
Expand Down Expand Up @@ -277,7 +277,7 @@ fn declToCompletion(builder: *Builder, decl_handle: Analyser.DeclWithHandle) err
if (ty.is_type_val and ty.data == .ip_index and ty.data.ip_index.index != null and !builder.analyser.ip.isUnknown(ty.data.ip_index.index.?)) {
break :blk try std.fmt.allocPrint(builder.arena, "{}", .{ty.fmtTypeVal(builder.analyser, .{ .truncate_container_decls = false })});
} else {
break :blk try std.fmt.allocPrint(builder.arena, "{}", .{ty.fmt(builder.analyser, .{ .truncate_container_decls = false })});
break :blk try std.fmt.allocPrint(builder.arena, "{}", .{try ty.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = false })});
}
} else null;

Expand Down Expand Up @@ -334,7 +334,7 @@ fn functionTypeCompletion(
const has_self_param = if (parent_container_ty) |container_ty| blk: {
if (container_ty.is_type_val) break :blk false;
if (container_ty.isNamespace()) break :blk false;
break :blk Analyser.firstParamIs(func_ty, container_ty.typeOf(builder.analyser));
break :blk Analyser.firstParamIs(func_ty, try container_ty.typeOf(builder.analyser));
} else false;

const insert_range, const replace_range, const new_text_format = prepareFunctionCompletion(builder);
Expand Down Expand Up @@ -408,7 +408,7 @@ fn functionTypeCompletion(
.snippet_placeholders = false,
})});

const description = try std.fmt.allocPrint(builder.arena, "{}", .{info.return_value.fmt(builder.analyser, .{ .truncate_container_decls = true })});
const description = try std.fmt.allocPrint(builder.arena, "{}", .{try info.return_value.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = true })});

break :blk .{
.detail = detail,
Expand Down Expand Up @@ -1437,7 +1437,7 @@ fn collectContainerFields(
};

const detail = if (try decl_handle.resolveType(builder.analyser)) |ty| detail: {
const type_fmt = ty.fmt(builder.analyser, .{ .truncate_container_decls = false });
const type_fmt = try ty.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = false });
if (field.ast.value_expr.unwrap()) |value_expr| {
const value_str = offsets.nodeToSlice(tree, value_expr);
break :detail try std.fmt.allocPrint(builder.arena, "{} = {s}", .{ type_fmt, value_str });
Expand Down Expand Up @@ -1465,7 +1465,8 @@ fn collectContainerFields(
if (!likely.allowsDeclLiterals()) continue;
// decl literal
var expected_ty = try decl_handle.resolveType(builder.analyser) orelse continue;
expected_ty = expected_ty.typeOf(builder.analyser).resolveDeclLiteralResultType();
expected_ty = try expected_ty.typeOf(builder.analyser);
expected_ty = expected_ty.resolveDeclLiteralResultType();
if (expected_ty.data != .container) continue;
if (!expected_ty.data.container.scope_handle.eql(container.data.container.scope_handle)) continue;
try declToCompletion(builder, decl_handle);
Expand Down Expand Up @@ -1507,14 +1508,14 @@ fn collectContainerNodes(

const gpa = builder.analyser.gpa;

var types_with_handles: std.ArrayListUnmanaged(Analyser.Type) = .empty;
var types_with_handles: Analyser.Type.ArraySet = .empty;
const token_index = switch (dot_context.type_info) {
.identifier_token_index => |token| token,
.expr_node_index => |node| {
if (try builder.analyser.resolveTypeOfNode(.of(node, handle))) |ty| {
try ty.getAllTypesWithHandlesArrayList(builder.arena, &types_with_handles);
try ty.getAllTypesWithHandlesArraySet(builder.analyser, &types_with_handles);
}
return types_with_handles.toOwnedSlice(builder.arena);
return types_with_handles.keys();
},
};
const source_index = offsets.tokenToLoc(handle.tree, token_index).end;
Expand All @@ -1532,8 +1533,8 @@ fn collectContainerNodes(
const var_decl = handle.tree.fullVarDecl(nodes[1]).?;
if (nodes[0].toOptional() == var_decl.ast.type_node) {
if (try builder.analyser.resolveTypeOfNode(.of(nodes[0], handle))) |ty| {
try ty.getAllTypesWithHandlesArrayList(builder.arena, &types_with_handles);
return types_with_handles.toOwnedSlice(builder.arena);
try ty.getAllTypesWithHandlesArraySet(builder.analyser, &types_with_handles);
return types_with_handles.keys();
}
}
},
Expand All @@ -1549,7 +1550,7 @@ fn collectContainerNodes(
.keyword => |token| try collectKeywordFnContainerNodes(builder, token, dot_context, &types_with_handles),
else => {},
}
return types_with_handles.toOwnedSlice(builder.arena);
return types_with_handles.keys();
}

fn resolveBuiltinFnArg(
Expand All @@ -1570,15 +1571,15 @@ fn collectBuiltinContainerNodes(
handle: *DocumentStore.Handle,
loc: offsets.Loc,
dot_context: EnumLiteralContext,
types_with_handles: *std.ArrayListUnmanaged(Analyser.Type),
types_with_handles: *Analyser.Type.ArraySet,
) error{OutOfMemory}!void {
if (dot_context.need_ret_type) return;
if (try resolveBuiltinFnArg(
builder.analyser,
dot_context.fn_arg_index,
handle.tree.source[loc.start..loc.end],
)) |ty| {
try types_with_handles.append(builder.arena, ty);
try ty.getAllTypesWithHandlesArraySet(builder.analyser, types_with_handles);
}
}

Expand All @@ -1587,16 +1588,15 @@ fn collectVarAccessContainerNodes(
handle: *DocumentStore.Handle,
loc: offsets.Loc,
dot_context: EnumLiteralContext,
types_with_handles: *std.ArrayListUnmanaged(Analyser.Type),
types_with_handles: *Analyser.Type.ArraySet,
) error{OutOfMemory}!void {
const analyser = builder.analyser;
const arena = builder.arena;

const symbol_decl = try analyser.lookupSymbolGlobal(handle, handle.tree.source[loc.start..loc.end], loc.end) orelse return;
const result = try symbol_decl.resolveType(analyser) orelse return;
const type_expr = try analyser.resolveDerefType(result) orelse result;
if (!type_expr.isFunc()) {
try type_expr.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try type_expr.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
return;
}

Expand All @@ -1605,21 +1605,21 @@ fn collectVarAccessContainerNodes(
if (dot_context.likely == .enum_comparison or dot_context.need_ret_type) { // => we need f()'s return type
var node_type = try analyser.resolveReturnType(type_expr) orelse return;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try node_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
return;
}
const param_index = dot_context.fn_arg_index;
if (param_index >= info.parameters.len) return;
const param_type = info.parameters[param_index].type;
try types_with_handles.append(arena, param_type);
try param_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
}

fn collectFieldAccessContainerNodes(
builder: *Builder,
handle: *DocumentStore.Handle,
loc: offsets.Loc,
dot_context: EnumLiteralContext,
types_with_handles: *std.ArrayListUnmanaged(Analyser.Type),
types_with_handles: *Analyser.Type.ArraySet,
) error{OutOfMemory}!void {
const analyser = builder.analyser;
const arena = builder.arena;
Expand All @@ -1633,12 +1633,12 @@ fn collectFieldAccessContainerNodes(
const container = try analyser.resolveDerefType(result) orelse result;
if (try analyser.resolveUnwrapErrorUnionType(container, .payload)) |unwrapped| {
if (unwrapped.isEnumType() or unwrapped.isUnionType()) {
try types_with_handles.append(arena, unwrapped);
try unwrapped.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
return;
}
}
// if (dot_context.likely == .enum_literal and !(container.isEnumType() or container.isUnionType())) return;
try container.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try container.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
return;
};
const name = offsets.locToSlice(handle.tree.source, name_loc);
Expand All @@ -1650,7 +1650,7 @@ fn collectFieldAccessContainerNodes(
if (try analyser.resolveOptionalUnwrap(node_type)) |unwrapped| node_type = unwrapped;
}
if (!node_type.isFunc()) {
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try node_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
continue;
}

Expand All @@ -1659,7 +1659,7 @@ fn collectFieldAccessContainerNodes(
if (dot_context.need_ret_type) { // => we need f()'s return type
node_type = try analyser.resolveReturnType(node_type) orelse continue;
if (try analyser.resolveUnwrapErrorUnionType(node_type, .payload)) |unwrapped| node_type = unwrapped;
try node_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try node_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
continue;
}
// don't have the luxury of referencing an `Ast.full.Call`
Expand All @@ -1682,7 +1682,7 @@ fn collectFieldAccessContainerNodes(
const param_index = dot_context.fn_arg_index + additional_index;
if (param_index >= params.len) continue;
const param_type = params[param_index].type;
try param_type.getAllTypesWithHandlesArrayList(arena, types_with_handles);
try param_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
}
}

Expand All @@ -1691,10 +1691,9 @@ fn collectEnumLiteralContainerNodes(
handle: *DocumentStore.Handle,
loc: offsets.Loc,
nodes: []const Ast.Node.Index,
types_with_handles: *std.ArrayListUnmanaged(Analyser.Type),
types_with_handles: *Analyser.Type.ArraySet,
) error{OutOfMemory}!void {
const analyser = builder.analyser;
const arena = builder.arena;
const alleged_field_name = offsets.locToSlice(handle.tree.source, offsets.identifierIndexToLoc(handle.tree.source, loc.start + 1, .name));
const dot_index = offsets.sourceIndexToTokenIndex(handle.tree, loc.start).pickPreferred(&.{.period}, &handle.tree) orelse return;
const el_dot_context = getSwitchOrStructInitContext(handle.tree, dot_index, nodes) orelse return;
Expand All @@ -1705,15 +1704,15 @@ fn collectEnumLiteralContainerNodes(
var member_type = try member_decl.resolveType(analyser) orelse continue;
// Unwrap `x{ .fld_w_opt_type =`
if (try analyser.resolveOptionalUnwrap(member_type)) |unwrapped| member_type = unwrapped;
try types_with_handles.append(arena, member_type);
try member_type.getAllTypesWithHandlesArraySet(analyser, types_with_handles);
}
}

fn collectKeywordFnContainerNodes(
builder: *Builder,
token: Ast.TokenIndex,
dot_context: EnumLiteralContext,
types_with_handles: *std.ArrayListUnmanaged(Analyser.Type),
types_with_handles: *Analyser.Type.ArraySet,
) error{OutOfMemory}!void {
const builtin_type_name: []const u8 = name: {
switch (builder.orig_handle.tree.tokenTag(token)) {
Expand All @@ -1729,5 +1728,5 @@ fn collectKeywordFnContainerNodes(
}
};
const ty = try builder.analyser.instanceStdBuiltinType(builtin_type_name) orelse return;
try types_with_handles.append(builder.arena, ty);
try ty.getAllTypesWithHandlesArraySet(builder.analyser, types_with_handles);
}
32 changes: 22 additions & 10 deletions src/features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,26 @@ fn hoverSymbolRecursive(
};

var referenced: Analyser.ReferencedType.Set = .empty;
var resolved_type_str: []const u8 = "unknown";
var resolved_type_strings: std.ArrayListUnmanaged([]const u8) = .empty;
if (try decl_handle.resolveType(analyser)) |resolved_type| {
if (try resolved_type.docComments(arena)) |doc|
try doc_strings.append(arena, doc);
resolved_type_str = try std.fmt.allocPrint(arena, "{}", .{resolved_type.fmt(analyser, .{
.referenced = &referenced,
.truncate_container_decls = false,
})});
const typeof = try resolved_type.typeOf(analyser);
const possible_types = try typeof.getAllTypesWithHandles(analyser);
for (possible_types) |ty| {
try resolved_type_strings.append(arena, try std.fmt.allocPrint(arena, "{}", .{ty.fmtTypeVal(analyser, .{
.referenced = &referenced,
.truncate_container_decls = possible_types.len > 1,
})}));
}
}
const referenced_types: []const Analyser.ReferencedType = referenced.keys();
return try hoverSymbolResolved(
arena,
markup_kind,
doc_strings.items,
def_str,
resolved_type_str,
resolved_type_strings.items,
referenced_types,
);
}
Expand All @@ -125,13 +129,17 @@ fn hoverSymbolResolved(
markup_kind: types.MarkupKind,
doc_strings: []const []const u8,
def_str: []const u8,
resolved_type_str: []const u8,
resolved_type_strings: []const []const u8,
referenced_types: []const Analyser.ReferencedType,
) error{OutOfMemory}![]const u8 {
var hover_text: std.ArrayListUnmanaged(u8) = .empty;
const writer = hover_text.writer(arena);
if (markup_kind == .markdown) {
try writer.print("```zig\n{s}\n```\n```zig\n({s})\n```", .{ def_str, resolved_type_str });
try writer.print("```zig\n{s}\n```", .{def_str});
for (resolved_type_strings) |resolved_type_str|
try writer.print("\n```zig\n({s})\n```", .{resolved_type_str});
if (resolved_type_strings.len == 0)
try writer.writeAll("\n```zig\n(unknown)\n```");
if (referenced_types.len > 0)
try writer.print("\n\n" ++ "Go to ", .{});
for (referenced_types, 0..) |ref, index| {
Expand All @@ -142,7 +150,11 @@ fn hoverSymbolResolved(
try writer.print("[{s}]({s}#L{d})", .{ ref.str, ref.handle.uri, line });
}
} else {
try writer.print("{s}\n({s})", .{ def_str, resolved_type_str });
try writer.print("{s}", .{def_str});
for (resolved_type_strings) |resolved_type_str|
try writer.print("\n({s})", .{resolved_type_str});
if (resolved_type_strings.len == 0)
try writer.writeAll("\n(unknown)");
}

if (doc_strings.len > 0) {
Expand Down Expand Up @@ -276,7 +288,7 @@ fn hoverDefinitionGlobal(
if (std.mem.eql(u8, name, "_")) return null;
if (try analyser.resolvePrimitive(name)) |ip_index| {
const resolved_type_str = try std.fmt.allocPrint(arena, "{}", .{analyser.ip.typeOf(ip_index).fmt(analyser.ip)});
break :blk try hoverSymbolResolved(arena, markup_kind, &.{}, name, resolved_type_str, &.{});
break :blk try hoverSymbolResolved(arena, markup_kind, &.{}, name, &.{resolved_type_str}, &.{});
}
}
const decl = (try analyser.lookupSymbolGlobal(handle, name, pos_index)) orelse return null;
Expand Down
6 changes: 3 additions & 3 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ fn typeStrOfNode(builder: *Builder, node: Ast.Node.Index) !?[]const u8 {
const type_str: []const u8 = try std.fmt.allocPrint(
builder.arena,
"{}",
.{resolved_type.fmt(builder.analyser, .{ .truncate_container_decls = true })},
.{try resolved_type.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = true })},
);
if (type_str.len == 0) return null;

Expand All @@ -347,7 +347,7 @@ fn typeStrOfToken(builder: *Builder, token: Ast.TokenIndex) !?[]const u8 {
const type_str: []const u8 = try std.fmt.allocPrint(
builder.arena,
"{}",
.{resolved_type.fmt(builder.analyser, .{ .truncate_container_decls = true })},
.{try resolved_type.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = true })},
);
if (type_str.len == 0) return null;

Expand Down Expand Up @@ -541,7 +541,7 @@ fn writeNodeInlayHint(
const type_str: []const u8 = try std.fmt.allocPrint(
builder.arena,
"{}",
.{ty.fmt(builder.analyser, .{ .truncate_container_decls = true })},
.{try ty.fmtTypeOf(builder.analyser, .{ .truncate_container_decls = true })},
);
if (type_str.len == 0) continue;
try appendTypeHintString(
Expand Down
2 changes: 1 addition & 1 deletion src/features/semantic_tokens.zig
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ fn colorIdentifierBasedOnType(
new_tok_mod.generic = true;
}

const has_self_param = builder.analyser.hasSelfParam(type_node);
const has_self_param = try builder.analyser.hasSelfParam(type_node);

try writeTokenMod(builder, target_tok, if (has_self_param) .method else .function, new_tok_mod);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/features/signature_help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ fn fnProtoToSignatureInfo(
})});

const arg_idx = if (skip_self_param) blk: {
const has_self_param = analyser.hasSelfParam(func_type);
const has_self_param = try analyser.hasSelfParam(func_type);
break :blk commas + @intFromBool(has_self_param);
} else commas;

Expand Down
Loading