717 lines
27 KiB
Zig
717 lines
27 KiB
Zig
const std = @import("std");
|
|
const zig_builtin = @import("builtin");
|
|
const DocumentStore = @import("DocumentStore.zig");
|
|
const analysis = @import("analysis.zig");
|
|
const types = @import("types.zig");
|
|
const offsets = @import("offsets.zig");
|
|
const Ast = std.zig.Ast;
|
|
const log = std.log.scoped(.inlay_hint);
|
|
const ast = @import("ast.zig");
|
|
const data = @import("data/data.zig");
|
|
const Config = @import("Config.zig");
|
|
|
|
/// don't show inlay hints for the given builtin functions
|
|
/// builtins with one parameter are skipped automatically
|
|
/// this option is rare and is therefore build-only and
|
|
/// non-configurable at runtime
|
|
pub const inlay_hints_exclude_builtins: []const u8 = &.{};
|
|
|
|
/// max number of children in a declaration/array-init/struct-init or similar
|
|
/// that will not get a visibility check
|
|
pub const inlay_hints_max_inline_children = 12;
|
|
|
|
/// checks whether node is inside the range
|
|
fn isNodeInRange(tree: Ast, node: Ast.Node.Index, range: types.Range) bool {
|
|
const endLocation = tree.tokenLocation(0, tree.lastToken(node));
|
|
if (endLocation.line < range.start.line) return false;
|
|
|
|
const beginLocation = tree.tokenLocation(0, tree.firstToken(node));
|
|
if (beginLocation.line > range.end.line) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
const Builder = struct {
|
|
allocator: std.mem.Allocator,
|
|
config: *const Config,
|
|
handle: *const DocumentStore.Handle,
|
|
hints: std.ArrayListUnmanaged(types.InlayHint),
|
|
hover_kind: types.MarkupContent.Kind,
|
|
encoding: offsets.Encoding,
|
|
|
|
fn deinit(self: *Builder) void {
|
|
for (self.hints.items) |hint| {
|
|
self.allocator.free(hint.tooltip.value);
|
|
}
|
|
self.hints.deinit(self.allocator);
|
|
}
|
|
|
|
fn appendParameterHint(self: *Builder, position: types.Position, label: []const u8, tooltip: []const u8, tooltip_noalias: bool, tooltip_comptime: bool) !void {
|
|
// TODO allocation could be avoided by extending InlayHint.jsonStringify
|
|
// adding tooltip_noalias & tooltip_comptime to InlayHint should be enough
|
|
const tooltip_text = blk: {
|
|
if (tooltip.len == 0) break :blk "";
|
|
const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else "";
|
|
|
|
if (self.hover_kind == .Markdown) {
|
|
break :blk try std.fmt.allocPrint(self.allocator, "```zig\n{s}{s}\n```", .{ prefix, tooltip });
|
|
}
|
|
|
|
break :blk try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ prefix, tooltip });
|
|
};
|
|
|
|
try self.hints.append(self.allocator, .{
|
|
.position = position,
|
|
.label = label,
|
|
.kind = types.InlayHintKind.Parameter,
|
|
.tooltip = .{
|
|
.kind = self.hover_kind,
|
|
.value = tooltip_text,
|
|
},
|
|
.paddingLeft = false,
|
|
.paddingRight = true,
|
|
});
|
|
}
|
|
|
|
fn toOwnedSlice(self: *Builder) []types.InlayHint {
|
|
return self.hints.toOwnedSlice(self.allocator);
|
|
}
|
|
};
|
|
|
|
/// `call` is the function call
|
|
/// `decl_handle` should be a function protototype
|
|
/// writes parameter hints into `builder.hints`
|
|
fn writeCallHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, call: Ast.full.Call, decl_handle: analysis.DeclWithHandle) !void {
|
|
const handle = builder.handle;
|
|
const tree = handle.tree;
|
|
|
|
const decl = decl_handle.decl;
|
|
const decl_tree = decl_handle.handle.tree;
|
|
|
|
switch (decl.*) {
|
|
.ast_node => |fn_node| {
|
|
var buffer: [1]Ast.Node.Index = undefined;
|
|
if (ast.fnProto(decl_tree, fn_node, &buffer)) |fn_proto| {
|
|
var i: usize = 0;
|
|
var it = fn_proto.iterate(&decl_tree);
|
|
|
|
if (try analysis.hasSelfParam(arena, store, decl_handle.handle, fn_proto)) {
|
|
_ = ast.nextFnParam(&it);
|
|
}
|
|
|
|
while (ast.nextFnParam(&it)) |param| : (i += 1) {
|
|
if (i >= call.ast.params.len) break;
|
|
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_param_token = tree.lastToken(call.ast.params[i]);
|
|
const param_name = tree.tokenSlice(last_param_token);
|
|
|
|
if (std.mem.eql(u8, param_name, name)) {
|
|
if (tree.firstToken(call.ast.params[i]) == last_param_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 token_tags = decl_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);
|
|
|
|
try builder.appendParameterHint(
|
|
offsets.tokenToPosition(tree, tree.firstToken(call.ast.params[i]), builder.encoding),
|
|
name,
|
|
tooltip,
|
|
no_alias,
|
|
comp_time,
|
|
);
|
|
}
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
/// takes parameter nodes from the ast and function parameter names from `Builtin.arguments` and writes parameter hints into `builder.hints`
|
|
fn writeBuiltinHint(builder: *Builder, parameters: []const Ast.Node.Index, arguments: []const []const u8) !void {
|
|
if (parameters.len == 0) return;
|
|
|
|
const handle = builder.handle;
|
|
const tree = handle.tree;
|
|
|
|
for (arguments) |arg, i| {
|
|
if (i >= parameters.len) break;
|
|
if (arg.len == 0) continue;
|
|
|
|
const colonIndex = std.mem.indexOfScalar(u8, arg, ':');
|
|
const type_expr: []const u8 = if (colonIndex) |index| arg[index + 1 ..] else &.{};
|
|
|
|
var label: ?[]const u8 = null;
|
|
var no_alias = false;
|
|
var comp_time = false;
|
|
|
|
var it = std.mem.split(u8, arg[0 .. colonIndex orelse arg.len], " ");
|
|
while (it.next()) |item| {
|
|
if (item.len == 0) continue;
|
|
label = item;
|
|
|
|
no_alias = no_alias or std.mem.eql(u8, item, "noalias");
|
|
comp_time = comp_time or std.mem.eql(u8, item, "comptime");
|
|
}
|
|
|
|
try builder.appendParameterHint(
|
|
offsets.tokenToPosition(tree, tree.firstToken(parameters[i]), builder.encoding),
|
|
label orelse "",
|
|
std.mem.trim(u8, type_expr, " \t\n"),
|
|
no_alias,
|
|
comp_time,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// takes a Ast.full.Call (a function call), analysis its function expression, finds its declaration and writes parameter hints into `builder.hints`
|
|
fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, call: Ast.full.Call) !void {
|
|
if (call.ast.params.len == 0) return;
|
|
if (builder.config.inlay_hints_exclude_single_argument and call.ast.params.len == 1) return;
|
|
|
|
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 location = tree.tokenLocation(0, main_tokens[call.ast.fn_expr]);
|
|
|
|
const absolute_index = location.line_start + location.column;
|
|
|
|
const name = tree.tokenSlice(main_tokens[call.ast.fn_expr]);
|
|
|
|
if (try analysis.lookupSymbolGlobal(store, arena, handle, name, absolute_index)) |decl_handle| {
|
|
try writeCallHint(builder, arena, store, call, decl_handle);
|
|
}
|
|
},
|
|
.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);
|
|
|
|
var held_range = try arena.allocator().dupeZ(u8, handle.text[start..rhs_loc.end]);
|
|
var tokenizer = std.zig.Tokenizer.init(held_range);
|
|
|
|
// note: we have the ast node, traversing it would probably yield better results
|
|
// than trying to re-tokenize and re-parse it
|
|
if (try analysis.getFieldAccessType(store, arena, handle, rhs_loc.end, &tokenizer)) |result| {
|
|
const container_handle = result.unwrapped orelse result.original;
|
|
switch (container_handle.type.data) {
|
|
.other => |container_handle_node| {
|
|
if (try analysis.lookupSymbolContainer(
|
|
store,
|
|
arena,
|
|
.{ .node = container_handle_node, .handle = container_handle.handle },
|
|
tree.tokenSlice(rhsToken),
|
|
true,
|
|
)) |decl_handle| {
|
|
try writeCallHint(builder, arena, store, call, decl_handle);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
},
|
|
else => {
|
|
log.debug("cannot deduce fn expression with tag '{}'", .{node_tags[call.ast.fn_expr]});
|
|
},
|
|
}
|
|
}
|
|
|
|
/// HACK self-hosted has not implemented async yet
|
|
fn callWriteNodeInlayHint(allocator: std.mem.Allocator, args: anytype) error{OutOfMemory}!void {
|
|
if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) {
|
|
const FrameSize = @sizeOf(@Frame(writeNodeInlayHint));
|
|
var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize);
|
|
defer allocator.free(child_frame);
|
|
|
|
return await @asyncCall(child_frame, {}, writeNodeInlayHint, args);
|
|
} else {
|
|
// TODO find a non recursive solution
|
|
return @call(.{}, writeNodeInlayHint, args);
|
|
}
|
|
}
|
|
|
|
/// iterates over the ast and writes parameter hints into `builder.hints` for every function call and builtin call
|
|
/// nodes outside the given range are excluded
|
|
fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, maybe_node: ?Ast.Node.Index, range: types.Range) error{OutOfMemory}!void {
|
|
const node = maybe_node orelse return;
|
|
|
|
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);
|
|
|
|
if (node == 0 or node > node_data.len) return;
|
|
|
|
var allocator = arena.allocator();
|
|
|
|
const tag = node_tags[node];
|
|
|
|
// NOTE traversing the ast instead of iterating over all nodes allows using visibility
|
|
// checks based on the given range which reduce runtimes by orders of magnitude for large files
|
|
switch (tag) {
|
|
.root => unreachable,
|
|
.call_one,
|
|
.call_one_comma,
|
|
.async_call_one,
|
|
.async_call_one_comma,
|
|
.call,
|
|
.call_comma,
|
|
.async_call,
|
|
.async_call_comma,
|
|
=> {
|
|
var params: [1]Ast.Node.Index = undefined;
|
|
const call = ast.callFull(tree, node, ¶ms).?;
|
|
try writeCallNodeHint(builder, arena, store, call);
|
|
|
|
for (call.ast.params) |param| {
|
|
if (call.ast.params.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, param, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param, range });
|
|
}
|
|
},
|
|
|
|
.builtin_call_two,
|
|
.builtin_call_two_comma,
|
|
.builtin_call,
|
|
.builtin_call_comma,
|
|
=> {
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
const params = ast.builtinCallParams(tree, node, &buffer).?;
|
|
|
|
if (builder.config.inlay_hints_show_builtin and params.len > 1) {
|
|
const name = tree.tokenSlice(main_tokens[node]);
|
|
|
|
outer: for (data.builtins) |builtin| {
|
|
if (!std.mem.eql(u8, builtin.name, name)) continue;
|
|
|
|
for (inlay_hints_exclude_builtins) |builtin_name| {
|
|
if (std.mem.eql(u8, builtin_name, name)) break :outer;
|
|
}
|
|
|
|
try writeBuiltinHint(builder, params, builtin.arguments);
|
|
}
|
|
}
|
|
|
|
for (params) |param| {
|
|
if (params.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, param, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param, range });
|
|
}
|
|
},
|
|
|
|
.optional_type,
|
|
.array_type,
|
|
.@"continue",
|
|
.anyframe_type,
|
|
.anyframe_literal,
|
|
.char_literal,
|
|
.number_literal,
|
|
.unreachable_literal,
|
|
.identifier,
|
|
.enum_literal,
|
|
.string_literal,
|
|
.multiline_string_literal,
|
|
.error_set_decl,
|
|
=> {},
|
|
|
|
.array_type_sentinel => {
|
|
const array_type = tree.arrayTypeSentinel(node);
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, array_type.ast.sentinel, range });
|
|
},
|
|
|
|
.ptr_type_aligned,
|
|
.ptr_type_sentinel,
|
|
.ptr_type,
|
|
.ptr_type_bit_range,
|
|
=> {
|
|
const ptr_type: Ast.full.PtrType = ast.ptrType(tree, node).?;
|
|
|
|
if (ptr_type.ast.sentinel != 0) {
|
|
return try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.sentinel, range });
|
|
}
|
|
|
|
if (ptr_type.ast.align_node != 0) {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.align_node, range });
|
|
|
|
if (ptr_type.ast.bit_range_start != 0) {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.bit_range_start, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.bit_range_end, range });
|
|
}
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.child_type, range });
|
|
},
|
|
|
|
.@"usingnamespace",
|
|
.field_access,
|
|
.unwrap_optional,
|
|
.bool_not,
|
|
.negation,
|
|
.bit_not,
|
|
.negation_wrap,
|
|
.address_of,
|
|
.@"try",
|
|
.@"await",
|
|
.deref,
|
|
.@"suspend",
|
|
.@"resume",
|
|
.@"return",
|
|
.grouped_expression,
|
|
.@"comptime",
|
|
.@"nosuspend",
|
|
=> try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }),
|
|
|
|
.test_decl,
|
|
.global_var_decl,
|
|
.local_var_decl,
|
|
.simple_var_decl,
|
|
.aligned_var_decl,
|
|
.@"errdefer",
|
|
.@"defer",
|
|
.@"break",
|
|
=> try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range }),
|
|
|
|
.@"catch",
|
|
.equal_equal,
|
|
.bang_equal,
|
|
.less_than,
|
|
.greater_than,
|
|
.less_or_equal,
|
|
.greater_or_equal,
|
|
.assign_mul,
|
|
.assign_div,
|
|
.assign_mod,
|
|
.assign_add,
|
|
.assign_sub,
|
|
.assign_shl,
|
|
.assign_shl_sat,
|
|
.assign_shr,
|
|
.assign_bit_and,
|
|
.assign_bit_xor,
|
|
.assign_bit_or,
|
|
.assign_mul_wrap,
|
|
.assign_add_wrap,
|
|
.assign_sub_wrap,
|
|
.assign_mul_sat,
|
|
.assign_add_sat,
|
|
.assign_sub_sat,
|
|
.assign,
|
|
.merge_error_sets,
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.array_mult,
|
|
.mul_wrap,
|
|
.mul_sat,
|
|
.add,
|
|
.sub,
|
|
.array_cat,
|
|
.add_wrap,
|
|
.sub_wrap,
|
|
.add_sat,
|
|
.sub_sat,
|
|
.shl,
|
|
.shl_sat,
|
|
.shr,
|
|
.bit_and,
|
|
.bit_xor,
|
|
.bit_or,
|
|
.@"orelse",
|
|
.bool_and,
|
|
.bool_or,
|
|
.array_access,
|
|
.switch_range,
|
|
.error_value,
|
|
.error_union,
|
|
=> {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range });
|
|
},
|
|
|
|
.slice_open,
|
|
.slice,
|
|
.slice_sentinel,
|
|
=> {
|
|
const slice: Ast.full.Slice = switch (tag) {
|
|
.slice => tree.slice(node),
|
|
.slice_open => tree.sliceOpen(node),
|
|
.slice_sentinel => tree.sliceSentinel(node),
|
|
else => unreachable,
|
|
};
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.sliced, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.start, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.end, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.sentinel, range });
|
|
},
|
|
|
|
.array_init_one,
|
|
.array_init_one_comma,
|
|
.array_init_dot_two,
|
|
.array_init_dot_two_comma,
|
|
.array_init_dot,
|
|
.array_init_dot_comma,
|
|
.array_init,
|
|
.array_init_comma,
|
|
=> {
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
const array_init: Ast.full.ArrayInit = switch (tag) {
|
|
.array_init, .array_init_comma => tree.arrayInit(node),
|
|
.array_init_one, .array_init_one_comma => tree.arrayInitOne(buffer[0..1], node),
|
|
.array_init_dot, .array_init_dot_comma => tree.arrayInitDot(node),
|
|
.array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(&buffer, node),
|
|
else => unreachable,
|
|
};
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, array_init.ast.type_expr, range });
|
|
for (array_init.ast.elements) |elem| {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, elem, range });
|
|
}
|
|
},
|
|
|
|
.struct_init_one,
|
|
.struct_init_one_comma,
|
|
.struct_init_dot_two,
|
|
.struct_init_dot_two_comma,
|
|
.struct_init_dot,
|
|
.struct_init_dot_comma,
|
|
.struct_init,
|
|
.struct_init_comma,
|
|
=> {
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
const struct_init: Ast.full.StructInit = switch (tag) {
|
|
.struct_init, .struct_init_comma => tree.structInit(node),
|
|
.struct_init_dot, .struct_init_dot_comma => tree.structInitDot(node),
|
|
.struct_init_one, .struct_init_one_comma => tree.structInitOne(buffer[0..1], node),
|
|
.struct_init_dot_two, .struct_init_dot_two_comma => tree.structInitDotTwo(&buffer, node),
|
|
else => unreachable,
|
|
};
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, struct_init.ast.type_expr, range });
|
|
|
|
for (struct_init.ast.fields) |field_init| {
|
|
if (struct_init.ast.fields.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, field_init, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, field_init, range });
|
|
}
|
|
},
|
|
|
|
.@"switch",
|
|
.switch_comma,
|
|
=> {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range });
|
|
|
|
const extra = tree.extraData(node_data[node].rhs, Ast.Node.SubRange);
|
|
const cases = tree.extra_data[extra.start..extra.end];
|
|
|
|
for (cases) |case_node| {
|
|
if (cases.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, case_node, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, case_node, range });
|
|
}
|
|
},
|
|
|
|
.switch_case_one,
|
|
.switch_case,
|
|
.switch_case_inline_one,
|
|
.switch_case_inline,
|
|
=> {
|
|
const switch_case = if (tag == .switch_case or tag == .switch_case_inline) tree.switchCase(node) else tree.switchCaseOne(node);
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, switch_case.ast.target_expr, range });
|
|
},
|
|
|
|
.while_simple,
|
|
.while_cont,
|
|
.@"while",
|
|
.for_simple,
|
|
.@"for",
|
|
=> {
|
|
const while_node = ast.whileAst(tree, node).?;
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.cond_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.cont_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.then_expr, range });
|
|
|
|
if (while_node.ast.else_expr != 0) {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.else_expr, range });
|
|
}
|
|
},
|
|
|
|
.if_simple,
|
|
.@"if",
|
|
=> {
|
|
const if_node = ast.ifFull(tree, node);
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.cond_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.then_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.else_expr, range });
|
|
},
|
|
|
|
.fn_proto_simple,
|
|
.fn_proto_multi,
|
|
.fn_proto_one,
|
|
.fn_proto,
|
|
.fn_decl,
|
|
=> {
|
|
var buffer: [1]Ast.Node.Index = undefined;
|
|
const fn_proto: Ast.full.FnProto = ast.fnProto(tree, node, &buffer).?;
|
|
|
|
var it = fn_proto.iterate(&tree);
|
|
while (ast.nextFnParam(&it)) |param_decl| {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param_decl.type_expr, range });
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.align_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.addrspace_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.section_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.callconv_expr, range });
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.return_type, range });
|
|
|
|
if (tag == .fn_decl) {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range });
|
|
}
|
|
},
|
|
|
|
.container_decl,
|
|
.container_decl_trailing,
|
|
.container_decl_two,
|
|
.container_decl_two_trailing,
|
|
.container_decl_arg,
|
|
.container_decl_arg_trailing,
|
|
.tagged_union,
|
|
.tagged_union_trailing,
|
|
.tagged_union_two,
|
|
.tagged_union_two_trailing,
|
|
.tagged_union_enum_tag,
|
|
.tagged_union_enum_tag_trailing,
|
|
=> {
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
const decl: Ast.full.ContainerDecl = ast.containerDecl(tree, node, &buffer).?;
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, decl.ast.arg, range });
|
|
|
|
for (decl.ast.members) |child| {
|
|
if (decl.ast.members.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, child, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, child, range });
|
|
}
|
|
},
|
|
|
|
.container_field_init,
|
|
.container_field_align,
|
|
.container_field,
|
|
=> {
|
|
const container_field = ast.containerField(tree, node).?;
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, container_field.ast.value_expr, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, container_field.ast.align_expr, range });
|
|
},
|
|
|
|
.block_two,
|
|
.block_two_semicolon,
|
|
=> {
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range });
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range });
|
|
},
|
|
|
|
.block,
|
|
.block_semicolon,
|
|
=> {
|
|
const subrange = tree.extra_data[node_data[node].lhs..node_data[node].rhs];
|
|
|
|
for (subrange) |child| {
|
|
if (subrange.len > inlay_hints_max_inline_children) {
|
|
if (!isNodeInRange(tree, child, range)) continue;
|
|
}
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, child, range });
|
|
}
|
|
},
|
|
|
|
.asm_simple,
|
|
.@"asm",
|
|
.asm_output,
|
|
.asm_input,
|
|
=> {
|
|
const asm_node: Ast.full.Asm = switch (tag) {
|
|
.@"asm" => tree.asmFull(node),
|
|
.asm_simple => tree.asmSimple(node),
|
|
else => return,
|
|
};
|
|
|
|
try callWriteNodeInlayHint(allocator, .{ builder, arena, store, asm_node.ast.template, range });
|
|
},
|
|
}
|
|
}
|
|
|
|
/// creates a list of `InlayHint`'s from the given document
|
|
/// only parameter hints are created
|
|
/// only hints in the given range are created
|
|
/// Caller owns returned memory.
|
|
/// `InlayHint.tooltip.value` has to deallocated separately
|
|
pub fn writeRangeInlayHint(
|
|
arena: *std.heap.ArenaAllocator,
|
|
config: Config,
|
|
store: *DocumentStore,
|
|
handle: *const DocumentStore.Handle,
|
|
range: types.Range,
|
|
hover_kind: types.MarkupContent.Kind,
|
|
encoding: offsets.Encoding,
|
|
) error{OutOfMemory}![]types.InlayHint {
|
|
var builder: Builder = .{
|
|
.allocator = arena.child_allocator,
|
|
.config = &config,
|
|
.handle = handle,
|
|
.hints = .{},
|
|
.hover_kind = hover_kind,
|
|
.encoding = encoding,
|
|
};
|
|
errdefer builder.deinit();
|
|
|
|
var buf: [2]Ast.Node.Index = undefined;
|
|
for (ast.declMembers(handle.tree, 0, &buf)) |child| {
|
|
if (!isNodeInRange(handle.tree, child, range)) continue;
|
|
try writeNodeInlayHint(&builder, arena, store, child, range);
|
|
}
|
|
|
|
return builder.toOwnedSlice();
|
|
}
|