simplify symbol references handler (#978)

This commit is contained in:
Techatrix 2023-02-06 09:25:29 +00:00 committed by GitHub
parent 69a1cae606
commit 1b3274aa9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 406 deletions

View File

@ -1395,14 +1395,18 @@ pub fn iterateChildren(
var buffer: [1]Node.Index = undefined;
const fn_proto = tree.fullFnProto(&buffer, node).?;
for (fn_proto.ast.params) |child| {
try callback(context, child);
var it = fn_proto.iterate(&tree);
while (nextFnParam(&it)) |param| {
try callback(context, param.type_expr);
}
try callback(context, fn_proto.ast.align_expr);
try callback(context, fn_proto.ast.addrspace_expr);
try callback(context, fn_proto.ast.section_expr);
try callback(context, fn_proto.ast.callconv_expr);
try callback(context, fn_proto.ast.return_type);
if (node_tags[node] == .fn_decl) {
try callback(context, node_data[node].rhs);
}
},
.container_decl_arg,

View File

@ -13,7 +13,7 @@ pub fn labelReferences(
encoding: offsets.Encoding,
include_decl: bool,
) error{OutOfMemory}!std.ArrayListUnmanaged(types.Location) {
std.debug.assert(decl.decl.* == .label_decl);
std.debug.assert(decl.decl.* == .label_decl); // use `symbolReferences` instead
const handle = decl.handle;
const tree = handle.tree;
const token_tags = tree.tokens.items(.tag);
@ -55,409 +55,127 @@ pub fn labelReferences(
const Builder = struct {
arena: *std.heap.ArenaAllocator,
locations: std.ArrayListUnmanaged(types.Location),
locations: std.ArrayListUnmanaged(types.Location) = .{},
/// this is the declaration we are searching for
decl_handle: analysis.DeclWithHandle,
store: *DocumentStore,
decl: analysis.DeclWithHandle,
encoding: offsets.Encoding,
pub fn init(arena: *std.heap.ArenaAllocator, store: *DocumentStore, decl: analysis.DeclWithHandle, encoding: offsets.Encoding) Builder {
return Builder{
.arena = arena,
.locations = .{},
.store = store,
.decl = decl,
.encoding = encoding,
};
}
const Context = struct {
builder: *Builder,
handle: *const DocumentStore.Handle,
};
pub fn add(self: *Builder, handle: *const DocumentStore.Handle, token_index: Ast.TokenIndex) !void {
pub fn add(self: *Builder, handle: *const DocumentStore.Handle, token_index: Ast.TokenIndex) error{OutOfMemory}!void {
try self.locations.append(self.arena.allocator(), .{
.uri = handle.uri,
.range = offsets.tokenToRange(handle.tree, token_index, self.encoding),
});
}
};
fn symbolReferencesInternal(
builder: *Builder,
node: Ast.Node.Index,
handle: *const DocumentStore.Handle,
is_root: bool,
) error{OutOfMemory}!void {
const tree = handle.tree;
if (!is_root and node == 0 or node > tree.nodes.len) return;
const node_tags = tree.nodes.items(.tag);
const datas = tree.nodes.items(.data);
const main_tokens = tree.nodes.items(.main_token);
const starts = tree.tokens.items(.start);
switch (node_tags[node]) {
.block,
.block_semicolon,
.block_two,
.block_two_semicolon,
=> {
var buffer: [2]Ast.Node.Index = undefined;
const statements = ast.blockStatements(tree, node, &buffer).?;
for (statements) |stmt|
try symbolReferencesInternal(builder, stmt, handle, false);
},
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.root,
=> {
var buf: [2]Ast.Node.Index = undefined;
const container_decl = tree.fullContainerDecl(&buf, node).?;
for (container_decl.ast.members) |member|
try symbolReferencesInternal(builder, member, handle, false);
},
.error_set_decl => {},
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
const var_decl = tree.fullVarDecl(node).?;
try symbolReferencesInternal(builder, var_decl.ast.type_node, handle, false);
try symbolReferencesInternal(builder, var_decl.ast.init_node, handle, false);
},
.container_field,
.container_field_align,
.container_field_init,
=> {
const field = tree.fullContainerField(node).?;
try symbolReferencesInternal(builder, field.ast.type_expr, handle, false);
try symbolReferencesInternal(builder, field.ast.value_expr, handle, false);
},
.identifier => {
const child = (try analysis.lookupSymbolGlobal(builder.store, builder.arena, handle, tree.getNodeSource(node), starts[main_tokens[node]])) orelse return;
if (std.meta.eql(builder.decl, child)) try builder.add(handle, main_tokens[node]);
},
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {
var buf: [1]Ast.Node.Index = undefined;
const fn_proto = tree.fullFnProto(&buf, node).?;
var it = fn_proto.iterate(&tree);
while (ast.nextFnParam(&it)) |param| {
try symbolReferencesInternal(builder, param.type_expr, handle, false);
}
try symbolReferencesInternal(builder, fn_proto.ast.return_type, handle, false);
try symbolReferencesInternal(builder, fn_proto.ast.align_expr, handle, false);
try symbolReferencesInternal(builder, fn_proto.ast.section_expr, handle, false);
try symbolReferencesInternal(builder, fn_proto.ast.callconv_expr, handle, false);
if (node_tags[node] == .fn_decl) {
try symbolReferencesInternal(builder, datas[node].rhs, handle, false);
}
},
.@"switch",
.switch_comma,
=> {
// TODO When renaming a union(enum) field, also rename switch items that refer to it.
try symbolReferencesInternal(builder, datas[node].lhs, handle, false);
const extra = tree.extraData(datas[node].rhs, Ast.Node.SubRange);
const cases = tree.extra_data[extra.start..extra.end];
for (cases) |case| {
try symbolReferencesInternal(builder, case, handle, false);
}
},
.switch_case_one,
.switch_case_inline_one,
.switch_case,
.switch_case_inline,
=> {
const case = tree.fullSwitchCase(node).?;
try symbolReferencesInternal(builder, case.ast.target_expr, handle, false);
for (case.ast.values) |val|
try symbolReferencesInternal(builder, val, handle, false);
},
.@"while",
.while_simple,
.while_cont,
.for_simple,
.@"for",
=> {
const loop = ast.fullWhile(tree, node).?;
try symbolReferencesInternal(builder, loop.ast.cond_expr, handle, false);
try symbolReferencesInternal(builder, loop.ast.then_expr, handle, false);
try symbolReferencesInternal(builder, loop.ast.cont_expr, handle, false);
try symbolReferencesInternal(builder, loop.ast.else_expr, handle, false);
},
.@"if",
.if_simple,
=> {
const if_node = ast.fullIf(tree, node).?;
try symbolReferencesInternal(builder, if_node.ast.cond_expr, handle, false);
try symbolReferencesInternal(builder, if_node.ast.then_expr, handle, false);
try symbolReferencesInternal(builder, if_node.ast.else_expr, handle, false);
},
.ptr_type,
.ptr_type_aligned,
.ptr_type_bit_range,
.ptr_type_sentinel,
=> {
const ptr_type = ast.fullPtrType(tree, node).?;
if (ptr_type.ast.align_node != 0) {
try symbolReferencesInternal(builder, ptr_type.ast.align_node, handle, false);
if (node_tags[node] == .ptr_type_bit_range) {
try symbolReferencesInternal(builder, ptr_type.ast.bit_range_start, handle, false);
try symbolReferencesInternal(builder, ptr_type.ast.bit_range_end, handle, false);
}
}
try symbolReferencesInternal(builder, ptr_type.ast.sentinel, handle, false);
try symbolReferencesInternal(builder, ptr_type.ast.child_type, handle, false);
},
.array_init,
.array_init_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const array_init = tree.fullArrayInit(&buf, node).?;
try symbolReferencesInternal(builder, array_init.ast.type_expr, handle, false);
for (array_init.ast.elements) |e|
try symbolReferencesInternal(builder, e, handle, false);
},
.struct_init,
.struct_init_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_one,
.struct_init_one_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const struct_init: Ast.full.StructInit = tree.fullStructInit(&buf, node).?;
try symbolReferencesInternal(builder, struct_init.ast.type_expr, handle, false);
for (struct_init.ast.fields) |field|
try symbolReferencesInternal(builder, field, handle, false);
},
.call,
.call_comma,
.call_one,
.call_one_comma,
.async_call,
.async_call_comma,
.async_call_one,
.async_call_one_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
const call = tree.fullCall(&buf, node).?;
try symbolReferencesInternal(builder, call.ast.fn_expr, handle, false);
for (call.ast.params) |param| {
try symbolReferencesInternal(builder, param, handle, false);
}
},
.slice,
.slice_sentinel,
.slice_open,
=> {
const slice: Ast.full.Slice = tree.fullSlice(node).?;
try symbolReferencesInternal(builder, slice.ast.sliced, handle, false);
try symbolReferencesInternal(builder, slice.ast.start, handle, false);
try symbolReferencesInternal(builder, slice.ast.end, handle, false);
try symbolReferencesInternal(builder, slice.ast.sentinel, handle, false);
},
.builtin_call,
.builtin_call_comma,
.builtin_call_two,
.builtin_call_two_comma,
=> {
var buffer: [2]Ast.Node.Index = undefined;
const params = ast.builtinCallParams(tree, node, &buffer).?;
for (params) |param|
try symbolReferencesInternal(builder, param, handle, false);
},
.@"asm",
.asm_simple,
=> |tag| {
const full_asm: Ast.full.Asm = if (tag == .@"asm") tree.asmFull(node) else tree.asmSimple(node);
if (full_asm.ast.items.len == 0)
try symbolReferencesInternal(builder, full_asm.ast.template, handle, false);
for (full_asm.inputs) |input|
try symbolReferencesInternal(builder, input, handle, false);
for (full_asm.outputs) |output|
try symbolReferencesInternal(builder, output, handle, false);
},
// TODO implement references for asm
.asm_output => {},
.asm_input => {},
.field_access => {
try symbolReferencesInternal(builder, datas[node].lhs, handle, false);
var bound_type_params = analysis.BoundTypeParams{};
const left_type = try analysis.resolveFieldAccessLhsType(
builder.store,
builder.arena,
(try analysis.resolveTypeOfNodeInternal(builder.store, builder.arena, .{
.node = datas[node].lhs,
.handle = handle,
}, &bound_type_params)) orelse return,
&bound_type_params,
);
const left_type_node = switch (left_type.type.data) {
.other => |n| n,
else => return,
};
const child = (try analysis.lookupSymbolContainer(
builder.store,
builder.arena,
.{ .node = left_type_node, .handle = left_type.handle },
tree.tokenSlice(datas[node].rhs),
!left_type.type.is_type_val,
)) orelse return;
if (std.meta.eql(child, builder.decl)) try builder.add(handle, datas[node].rhs);
},
.@"usingnamespace",
.unwrap_optional,
.bool_not,
.negation,
.bit_not,
.negation_wrap,
.address_of,
.@"try",
.@"await",
.optional_type,
.deref,
.@"suspend",
.@"resume",
.@"continue",
.@"break",
.@"return",
.grouped_expression,
.@"comptime",
.@"nosuspend",
=> try symbolReferencesInternal(builder, datas[node].lhs, handle, false),
.test_decl,
.@"errdefer",
.@"defer",
.anyframe_type,
=> try symbolReferencesInternal(builder, datas[node].rhs, handle, false),
.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_type,
.array_type_sentinel,
.array_access,
.@"catch",
.switch_range,
.error_union,
=> {
try symbolReferencesInternal(builder, datas[node].lhs, handle, false);
try symbolReferencesInternal(builder, datas[node].rhs, handle, false);
},
.anyframe_literal,
.char_literal,
.number_literal,
.unreachable_literal,
.enum_literal,
.string_literal,
.multiline_string_literal,
.error_value,
=> {},
fn collectReferences(self: *Builder, handle: *const DocumentStore.Handle, node: Ast.Node.Index) error{OutOfMemory}!void {
const context = Context{
.builder = self,
.handle = handle,
};
try ast.iterateChildrenRecursive(handle.tree, node, &context, error{OutOfMemory}, referenceNode);
}
}
fn referenceNode(self: *const Context, node: Ast.Node.Index) error{OutOfMemory}!void {
const builder = self.builder;
const handle = self.handle;
const node_tags = handle.tree.nodes.items(.tag);
const datas = handle.tree.nodes.items(.data);
const main_tokens = handle.tree.nodes.items(.main_token);
const starts = handle.tree.tokens.items(.start);
switch (node_tags[node]) {
.identifier => {
const identifier_token = main_tokens[node];
const child = (try analysis.lookupSymbolGlobal(
builder.store,
builder.arena,
handle,
offsets.tokenToSlice(handle.tree, identifier_token),
starts[identifier_token],
)) orelse return;
if (std.meta.eql(builder.decl_handle, child)) {
try builder.add(handle, identifier_token);
}
},
.field_access => {
var bound_type_params = analysis.BoundTypeParams{};
const left_type = try analysis.resolveFieldAccessLhsType(
builder.store,
builder.arena,
(try analysis.resolveTypeOfNodeInternal(
builder.store,
builder.arena,
.{ .node = datas[node].lhs, .handle = handle },
&bound_type_params,
)) orelse return,
&bound_type_params,
);
const left_type_node = switch (left_type.type.data) {
.other => |n| n,
else => return,
};
const child = (try analysis.lookupSymbolContainer(
builder.store,
builder.arena,
.{ .node = left_type_node, .handle = left_type.handle },
offsets.tokenToSlice(handle.tree, datas[node].rhs),
!left_type.type.is_type_val,
)) orelse return;
if (std.meta.eql(builder.decl_handle, child)) {
try builder.add(handle, datas[node].rhs);
}
},
else => {},
}
}
};
pub fn symbolReferences(
arena: *std.heap.ArenaAllocator,
store: *DocumentStore,
decl_handle: analysis.DeclWithHandle,
encoding: offsets.Encoding,
/// add `decl_handle` as a references
include_decl: bool,
/// exclude references from the std library
skip_std_references: bool,
/// search other files for references
workspace: bool,
) error{OutOfMemory}!std.ArrayListUnmanaged(types.Location) {
std.debug.assert(decl_handle.decl.* != .label_decl);
std.debug.assert(decl_handle.decl.* != .label_decl); // use `labelReferences` instead
var builder = Builder.init(arena, store, decl_handle, encoding);
var builder = Builder{
.arena = arena,
.store = store,
.decl_handle = decl_handle,
.encoding = encoding,
};
const curr_handle = decl_handle.handle;
if (include_decl) try builder.add(curr_handle, decl_handle.nameToken());
switch (decl_handle.decl.*) {
.ast_node => {
try symbolReferencesInternal(&builder, 0, curr_handle, true);
.ast_node,
.pointer_payload,
.switch_payload,
.array_payload,
.array_index,
=> {
try builder.collectReferences(curr_handle, 0);
if (!workspace) return builder.locations;
if (decl_handle.decl.* != .ast_node or !workspace) return builder.locations;
var dependencies = std.StringArrayHashMapUnmanaged(void){};
@ -476,24 +194,14 @@ pub fn symbolReferences(
}
for (dependencies.keys()) |uri| {
if (std.mem.eql(u8, uri, curr_handle.uri)) continue;
const handle = store.getHandle(uri) orelse continue;
if (std.mem.eql(u8, handle.uri, curr_handle.uri)) continue;
try symbolReferencesInternal(&builder, 0, handle, true);
try builder.collectReferences(handle, 0);
}
},
.pointer_payload,
.switch_payload,
.array_payload,
.array_index,
=> {
try symbolReferencesInternal(&builder, 0, curr_handle, true);
return builder.locations;
},
.param_payload => |pay| blk: {
.param_payload => |payload| blk: {
// Rename the param tok.
const param = pay.param;
for (curr_handle.document_scope.scopes.items(.data)) |scope_data| {
if (scope_data != .function) continue;
@ -504,10 +212,10 @@ pub fn symbolReferences(
var it = fn_proto.iterate(&curr_handle.tree);
while (ast.nextFnParam(&it)) |candidate| {
if (!std.meta.eql(candidate, param)) continue;
if (!std.meta.eql(candidate, payload.param)) continue;
if (curr_handle.tree.nodes.items(.tag)[proto] != .fn_decl) break :blk;
try symbolReferencesInternal(&builder, curr_handle.tree.nodes.items(.data)[proto].rhs, curr_handle, false);
try builder.collectReferences(curr_handle, curr_handle.tree.nodes.items(.data)[proto].rhs);
break :blk;
}
}

View File

@ -11,9 +11,6 @@ const offsets = zls.offsets;
const allocator: std.mem.Allocator = std.testing.allocator;
// TODO fix references so that we can stop skipping these tests
const skip_references_tests = true;
test "references" {
try testReferences(
\\const <0> = 0;
@ -59,7 +56,7 @@ test "references - local scope" {
\\ return <0> + bar;
\\}
);
if (skip_references_tests) return error.SkipZigTest;
if (true) return error.SkipZigTest; // TODO
try testReferences(
\\const foo = blk: {
\\ _ = blk: {
@ -73,6 +70,32 @@ test "references - local scope" {
);
}
test "references - struct field access" {
if (true) return error.SkipZigTest; // TODO
try testReferences(
\\const S = struct {placeholder: u32 = 3};
\\pub fn foo() bool {
\\ const s: S = .{};
\\ return s.<0> == s.<0>;
\\}
);
}
test "references - struct decl access" {
try testReferences(
\\const S = struct {
\\ fn <0>() void {}
\\};
\\pub fn foo() bool {
\\ const s: S = .{};
\\ s.<0>();
\\ s.<0>();
\\ <1>();
\\}
\\fn <1>() void {}
);
}
test "references - while continue expression" {
try testReferences(
\\ pub fn foo() void {
@ -83,7 +106,7 @@ test "references - while continue expression" {
}
test "references - label" {
if (skip_references_tests) return error.SkipZigTest;
if (true) return error.SkipZigTest; // TODO
try testReferences(
\\const foo = <0>: {
\\ break :<0> 0;
@ -106,6 +129,8 @@ fn testReferences(source: []const u8) !void {
try ctx.requestDidOpen(file_uri, phr.new_source);
try std.testing.expect(phr.locations.len != 0);
var i: usize = 0;
while (i < phr.locations.len) : (i += 1) {
const var_loc = phr.locations.items(.old)[i];
@ -120,6 +145,14 @@ fn testReferences(source: []const u8) !void {
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", params);
var error_builder = ErrorBuilder.init(allocator, phr.new_source);
defer error_builder.deinit();
errdefer {
const note_loc = phr.locations.items(.new)[i];
error_builder.msgAtLoc("asked for references here", note_loc, .info, .{}) catch {};
error_builder.writeDebug();
}
const locations: []types.Location = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{});
return error.InvalidResponse;
@ -150,14 +183,6 @@ fn testReferences(source: []const u8) !void {
};
defer allocator.free(expected_locs);
var error_builder = ErrorBuilder.init(allocator, phr.new_source);
defer error_builder.deinit();
errdefer {
const note_loc = phr.locations.items(.new)[i];
error_builder.msgAtLoc("asked for references here", note_loc, .info, .{}) catch {};
error_builder.writeDebug();
}
// keeps track of expected locations that have been given by the server
// used to detect double references and missing references
var visited = try std.DynamicBitSetUnmanaged.initEmpty(allocator, expected_locs.len);