Skip to content
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

various improvements to inlay hints #1784

Merged
merged 7 commits into from
Feb 19, 2024
45 changes: 26 additions & 19 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1257,25 +1257,8 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
}
}

if (try analyser.lookupSymbolGlobal(
handle,
name,
starts[name_token],
)) |child| {
switch (child.decl) {
.ast_node => |n| {
if (n == node) return null;
const child_decl_tree = child.handle.tree;
if (child_decl_tree.fullVarDecl(n)) |var_decl| {
if (var_decl.ast.init_node == node)
return null;
}
},
else => {},
}
return try child.resolveType(analyser);
}
return null;
const child = try analyser.lookupSymbolGlobal(handle, name, starts[name_token]) orelse return null;
return try child.resolveType(analyser);
},
.call,
.call_comma,
Expand Down Expand Up @@ -2647,6 +2630,30 @@ pub const Type = struct {
}
try writer.writeAll(offsets.nodeToSlice(tree, node));
},
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {
var buf: [1]Ast.Node.Index = undefined;
const fn_proto = node_handle.handle.tree.fullFnProto(&buf, node_handle.node).?;

try writer.print("{}", .{fmtFunction(.{
.fn_proto = fn_proto,
.tree = &node_handle.handle.tree,
.include_fn_keyword = true,
.include_name = false,
.skip_first_param = false,
.parameters = .{ .show = .{
.include_modifiers = true,
.include_names = true,
.include_types = true,
} },
.include_return_type = true,
.snippet_placeholders = false,
})});
},
else => try writer.writeAll(offsets.nodeToSlice(node_handle.handle.tree, node_handle.node)),
},
.ip_index => |payload| try analyser.ip.print(payload.index, writer, .{}),
Expand Down
97 changes: 30 additions & 67 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,79 +92,67 @@ const Builder = struct {
};

/// `call` is the function call
/// `decl_handle` should be a function protototype
/// `ty` should be a function protototype
/// writes parameter hints into `builder.hints`
fn writeCallHint(builder: *Builder, call: Ast.full.Call, decl_handle: Analyser.DeclWithHandle) !void {
fn writeCallHint(builder: *Builder, call: Ast.full.Call, ty: Analyser.Type) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

const handle = builder.handle;
const tree = handle.tree;

const node = switch (decl_handle.decl) {
.ast_node => |node| node,
else => return,
};

const maybe_resolved_alias = try builder.analyser.resolveVarDeclAlias(.{ .node = node, .handle = decl_handle.handle });
const resolved_decl_handle = if (maybe_resolved_alias) |resolved_decl| resolved_decl else decl_handle;

const fn_node = switch (resolved_decl_handle.decl) {
.ast_node => |fn_node| fn_node,
else => return,
};

const decl_tree = resolved_decl_handle.handle.tree;
const fn_ty = try builder.analyser.resolveFuncProtoOfCallable(ty) orelse return;
const fn_node = fn_ty.data.other; // this assumes that function types can only be Ast nodes

var buffer: [1]Ast.Node.Index = undefined;
const fn_proto = decl_tree.fullFnProto(&buffer, fn_node) orelse return;
const fn_proto = fn_node.handle.tree.fullFnProto(&buffer, fn_node.node).?;

var params = try std.ArrayListUnmanaged(Ast.full.FnProto.Param).initCapacity(builder.arena, fn_proto.ast.params.len);
defer params.deinit(builder.arena);

var it = fn_proto.iterate(&decl_tree);
var it = fn_proto.iterate(&fn_node.handle.tree);
while (ast.nextFnParam(&it)) |param| {
try params.append(builder.arena, param);
}

const has_self_param = call.ast.params.len + 1 == params.items.len and
try builder.analyser.isInstanceCall(handle, call, resolved_decl_handle.handle, fn_proto);
try builder.analyser.isInstanceCall(handle, call, fn_node.handle, fn_proto);

const parameters = params.items[@intFromBool(has_self_param)..];
const arguments = call.ast.params;
const min_len = @min(parameters.len, arguments.len);
for (parameters[0..min_len], arguments[0..min_len]) |param, arg| {
const name_token = param.name_token orelse continue;
const name = decl_tree.tokenSlice(name_token);

if (builder.config.inlay_hints_hide_redundant_param_names or builder.config.inlay_hints_hide_redundant_param_names_last_token) {
const last_arg_token = ast.lastToken(tree, arg);
const arg_name = tree.tokenSlice(last_arg_token);

if (std.mem.eql(u8, arg_name, name)) {
if (tree.firstToken(arg) == last_arg_token) {
if (builder.config.inlay_hints_hide_redundant_param_names)
continue;
} else {
if (builder.config.inlay_hints_hide_redundant_param_names_last_token)
continue;
}
}
const parameter_name_token = param.name_token orelse continue;
const parameter_name = offsets.identifierTokenToNameSlice(fn_node.handle.tree, parameter_name_token);

if (builder.config.inlay_hints_hide_redundant_param_names or builder.config.inlay_hints_hide_redundant_param_names_last_token) dont_skip: {
const arg_token = if (builder.config.inlay_hints_hide_redundant_param_names_last_token)
ast.lastToken(tree, arg)
else if (builder.config.inlay_hints_hide_redundant_param_names)
tree.nodes.items(.main_token)[arg]
else
unreachable;

if (tree.tokens.items(.tag)[arg_token] != .identifier) break :dont_skip;
const arg_token_name = offsets.identifierTokenToNameSlice(tree, arg_token);
if (!std.mem.eql(u8, parameter_name, arg_token_name)) break :dont_skip;

continue;
}

const token_tags = decl_tree.tokens.items(.tag);
const token_tags = fn_node.handle.tree.tokens.items(.tag);

const no_alias = if (param.comptime_noalias) |t| token_tags[t] == .keyword_noalias or token_tags[t - 1] == .keyword_noalias else false;
const comp_time = if (param.comptime_noalias) |t| token_tags[t] == .keyword_comptime or token_tags[t - 1] == .keyword_comptime else false;

const tooltip = if (param.anytype_ellipsis3) |token|
if (token_tags[token] == .keyword_anytype) "anytype" else ""
else
offsets.nodeToSlice(decl_tree, param.type_expr);
offsets.nodeToSlice(fn_node.handle.tree, param.type_expr);

try builder.appendParameterHint(
tree.firstToken(arg),
name,
parameter_name,
tooltip,
no_alias,
comp_time,
Expand Down Expand Up @@ -312,40 +300,15 @@ fn writeCallNodeHint(builder: *Builder, call: Ast.full.Call) !void {
const handle = builder.handle;
const tree = handle.tree;
const node_tags = tree.nodes.items(.tag);
const node_data = tree.nodes.items(.data);
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);

switch (node_tags[call.ast.fn_expr]) {
.identifier => {
const name_token = main_tokens[call.ast.fn_expr];
const name = offsets.identifierTokenToNameSlice(tree, name_token);
const source_index = offsets.tokenToIndex(tree, name_token);

if (try builder.analyser.lookupSymbolGlobal(handle, name, source_index)) |decl_handle| {
try writeCallHint(builder, call, decl_handle);
}
const ty = try builder.analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return;
try writeCallHint(builder, call, ty);
},
.field_access => {
const lhsToken = tree.firstToken(call.ast.fn_expr);
const rhsToken = node_data[call.ast.fn_expr].rhs;
std.debug.assert(token_tags[rhsToken] == .identifier);

const start = offsets.tokenToIndex(tree, lhsToken);
const rhs_loc = offsets.tokenToLoc(tree, rhsToken);

// note: we have the ast node, traversing it would probably yield better results
// than trying to re-tokenize and re-parse it
if (try builder.analyser.getFieldAccessType(handle, rhs_loc.end, .{
.start = start,
.end = rhs_loc.end,
})) |ty| {
const container_handle = try builder.analyser.resolveDerefType(ty) orelse ty;
const symbol = offsets.identifierTokenToNameSlice(tree, rhsToken);
if (try container_handle.lookupSymbol(builder.analyser, symbol)) |decl_handle| {
try writeCallHint(builder, call, decl_handle);
}
}
const ty = try builder.analyser.resolveTypeOfNode(.{ .node = call.ast.fn_expr, .handle = handle }) orelse return;
try writeCallHint(builder, call, ty);
},
else => {
log.debug("cannot deduce fn expression with tag '{}'", .{node_tags[call.ast.fn_expr]});
Expand Down
Loading