Merge pull request #79 from alexnask/new_parser

Store trees in handles, reparse on text changes.
This commit is contained in:
Alexandros Naskos 2020-05-24 19:21:41 +03:00 committed by GitHub
commit 075c364767
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 81 deletions

View File

@ -262,10 +262,10 @@ fn findReturnStatement(tree: *ast.Tree, fn_decl: *ast.Node.FnProto) ?*ast.Node.C
/// Resolves the return type of a function
fn resolveReturnType(analysis_ctx: *AnalysisContext, fn_decl: *ast.Node.FnProto) ?*ast.Node {
if (isTypeFunction(analysis_ctx.tree, fn_decl) and fn_decl.body_node != null) {
if (isTypeFunction(analysis_ctx.tree(), fn_decl) and fn_decl.body_node != null) {
// If this is a type function and it only contains a single return statement that returns
// a container declaration, we will return that declaration.
const ret = findReturnStatement(analysis_ctx.tree, fn_decl) orelse return null;
const ret = findReturnStatement(analysis_ctx.tree(), fn_decl) orelse return null;
if (ret.rhs) |rhs|
if (resolveTypeOfNode(analysis_ctx, rhs)) |res_rhs| switch (res_rhs.id) {
.ContainerDecl => {
@ -293,7 +293,7 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
return resolveTypeOfNode(analysis_ctx, vari.type_node orelse vari.init_node.?) orelse null;
},
.Identifier => {
if (getChildOfSlice(analysis_ctx.tree, analysis_ctx.scope_nodes, analysis_ctx.tree.getNodeSource(node))) |child| {
if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, analysis_ctx.tree().getNodeSource(node))) |child| {
return resolveTypeOfNode(analysis_ctx, child);
} else return null;
},
@ -323,11 +323,11 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
.Period => {
// Save the child string from this tree since the tree may switch when processing
// an import lhs.
var rhs_str = nodeToString(analysis_ctx.tree, infix_op.rhs) orelse return null;
var rhs_str = nodeToString(analysis_ctx.tree(), infix_op.rhs) orelse return null;
// Use the analysis context temporary arena to store the rhs string.
rhs_str = std.mem.dupe(&analysis_ctx.arena.allocator, u8, rhs_str) catch return null;
const left = resolveTypeOfNode(analysis_ctx, infix_op.lhs) orelse return null;
const child = getChild(analysis_ctx.tree, left, rhs_str) orelse return null;
const child = getChild(analysis_ctx.tree(), left, rhs_str) orelse return null;
return resolveTypeOfNode(analysis_ctx, child);
},
else => {},
@ -338,7 +338,7 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
switch (prefix_op.op) {
.SliceType, .ArrayType => return node,
.PtrType => {
const op_token_id = analysis_ctx.tree.token_ids[prefix_op.op_token];
const op_token_id = analysis_ctx.tree().token_ids[prefix_op.op_token];
switch (op_token_id) {
.Asterisk => return resolveTypeOfNode(analysis_ctx, prefix_op.rhs),
.LBracket, .AsteriskAsterisk => return null,
@ -361,7 +361,7 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
},
.BuiltinCall => {
const builtin_call = node.cast(ast.Node.BuiltinCall).?;
const call_name = analysis_ctx.tree.tokenSlice(builtin_call.builtin_token);
const call_name = analysis_ctx.tree().tokenSlice(builtin_call.builtin_token);
if (std.mem.eql(u8, call_name, "@This")) {
if (builtin_call.params_len != 0) return null;
return analysis_ctx.in_container;
@ -373,7 +373,7 @@ pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.
const import_param = builtin_call.paramsConst()[0];
if (import_param.id != .StringLiteral) return null;
const import_str = analysis_ctx.tree.tokenSlice(import_param.cast(ast.Node.StringLiteral).?.token);
const import_str = analysis_ctx.tree().tokenSlice(import_param.cast(ast.Node.StringLiteral).?.token);
return analysis_ctx.onImport(import_str[1 .. import_str.len - 1]) catch |err| block: {
std.debug.warn("Error {} while processing import {}\n", .{ err, import_str });
break :block null;
@ -442,7 +442,7 @@ pub fn getFieldAccessTypeNode(
switch (next.id) {
.Eof => return current_node,
.Identifier => {
if (getChildOfSlice(analysis_ctx.tree, analysis_ctx.scope_nodes, tokenizer.buffer[next.loc.start..next.loc.end])) |child| {
if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, tokenizer.buffer[next.loc.start..next.loc.end])) |child| {
if (resolveTypeOfNode(analysis_ctx, child)) |node_type| {
current_node = node_type;
} else return null;
@ -456,7 +456,7 @@ pub fn getFieldAccessTypeNode(
// TODO: This works for now, maybe we should filter based on the partial identifier ourselves?
if (after_period.loc.end == line_length) return current_node;
if (getChild(analysis_ctx.tree, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end])) |child| {
if (getChild(analysis_ctx.tree(), current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end])) |child| {
if (resolveTypeOfNode(analysis_ctx, child)) |child_type| {
current_node = child_type;
} else return null;

View File

@ -9,15 +9,11 @@ pub const Handle = struct {
document: types.TextDocument,
count: usize,
import_uris: std.ArrayList([]const u8),
tree: *std.zig.ast.Tree,
pub fn uri(handle: Handle) []const u8 {
return handle.document.uri;
}
/// Returns a zig AST, with all its errors.
pub fn tree(handle: Handle, allocator: *std.mem.Allocator) !*std.zig.ast.Tree {
return try std.zig.parse(allocator, handle.document.text);
}
};
allocator: *std.mem.Allocator,
@ -45,9 +41,11 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) !*Handle {
.text = text,
.mem = text,
},
.tree = try std.zig.parse(self.allocator, text),
};
const kv = try self.handles.getOrPutValue(uri, handle);
return kv.value;
try self.handles.putNoClobber(uri, handle);
return handle;
}
pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*Handle {
@ -73,6 +71,8 @@ fn decrementCount(self: *DocumentStore, uri: []const u8) void {
return;
std.debug.warn("Freeing document: {}\n", .{uri});
entry.value.tree.deinit();
self.allocator.free(entry.value.document.mem);
for (entry.value.import_uris.items) |import_uri| {
@ -102,8 +102,11 @@ pub fn getHandle(self: *DocumentStore, uri: []const u8) ?*Handle {
}
// Check if the document text is now sane, move it to sane_text if so.
fn removeOldImports(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const u8) !void {
fn refreshDocument(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const u8) !void {
std.debug.warn("New text for document {}\n", .{handle.uri()});
handle.tree.deinit();
handle.tree = try std.zig.parse(self.allocator, handle.document.text);
// TODO: Better algorithm or data structure?
// Removing the imports is costly since they live in an array list
// Perhaps we should use an AutoHashMap([]const u8, {}) ?
@ -111,14 +114,11 @@ fn removeOldImports(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]cons
// Try to detect removed imports and decrement their counts.
if (handle.import_uris.items.len == 0) return;
const tree = try handle.tree(self.allocator);
defer tree.deinit();
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
var import_strs = std.ArrayList([]const u8).init(&arena.allocator);
try analysis.collectImports(&import_strs, tree);
try analysis.collectImports(&import_strs, handle.tree);
const still_exist = try arena.allocator.alloc(bool, handle.import_uris.items.len);
for (still_exist) |*ex| {
@ -150,7 +150,7 @@ fn removeOldImports(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]cons
const uri = handle.import_uris.orderedRemove(idx - offset);
offset += 1;
self.closeDocument(uri);
self.decrementCount(uri);
self.allocator.free(uri);
}
}
@ -213,7 +213,7 @@ pub fn applyChanges(
}
}
try self.removeOldImports(handle, zig_lib_path);
try self.refreshDocument(handle, zig_lib_path);
}
pub fn uriFromImportStr(
@ -254,16 +254,19 @@ pub const AnalysisContext = struct {
// This arena is used for temporary allocations while analyzing,
// not for the tree allocations.
arena: *std.heap.ArenaAllocator,
tree: *std.zig.ast.Tree,
scope_nodes: []*std.zig.ast.Node,
in_container: *std.zig.ast.Node,
std_uri: ?[]const u8,
pub fn tree(self: AnalysisContext) *std.zig.ast.Tree {
return self.handle.tree;
}
fn refreshScopeNodes(self: *AnalysisContext) !void {
var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&self.arena.allocator);
try analysis.addChildrenNodes(&scope_nodes, self.tree, &self.tree.root_node.base);
try analysis.addChildrenNodes(&scope_nodes, self.tree(), &self.tree().root_node.base);
self.scope_nodes = scope_nodes.items;
self.in_container = &self.tree.root_node.base;
self.in_container = &self.tree().root_node.base;
}
pub fn onContainer(self: *AnalysisContext, container: *std.zig.ast.Node.ContainerDecl) !void {
@ -271,7 +274,7 @@ pub const AnalysisContext = struct {
self.in_container = &container.base;
var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&self.arena.allocator);
try analysis.addChildrenNodes(&scope_nodes, self.tree, &container.base);
try analysis.addChildrenNodes(&scope_nodes, self.tree(), &container.base);
self.scope_nodes = scope_nodes.items;
}
}
@ -295,11 +298,8 @@ pub const AnalysisContext = struct {
// If we did, set our new handle and return the parsed tree root node.
if (std.mem.eql(u8, uri, final_uri)) {
self.handle = self.store.getHandle(final_uri) orelse return null;
self.tree.deinit();
self.tree = try self.handle.tree(allocator);
try self.refreshScopeNodes();
return &self.tree.root_node.base;
return &self.tree().root_node.base;
}
}
@ -309,11 +309,8 @@ pub const AnalysisContext = struct {
// If it is, increment the count, set our new handle and return the parsed tree root node.
new_handle.count += 1;
self.handle = new_handle;
self.tree.deinit();
self.tree = try self.handle.tree(allocator);
try self.refreshScopeNodes();
return &self.tree.root_node.base;
return &self.tree().root_node.base;
}
// New document, read the file then call into openDocument.
@ -341,38 +338,27 @@ pub const AnalysisContext = struct {
try self.handle.import_uris.append(final_uri);
consumed_final_uri = true;
// Swap handles and get new tree.
// Swap handles.
// This takes ownership of the passed uri and text.
const duped_final_uri = try std.mem.dupe(allocator, u8, final_uri);
errdefer allocator.free(duped_final_uri);
self.handle = try newDocument(self.store, duped_final_uri, file_contents);
}
// Free old tree, add new one if it exists.
// If we return null, no one should access the tree.
self.tree.deinit();
self.tree = try self.handle.tree(allocator);
try self.refreshScopeNodes();
return &self.tree.root_node.base;
return &self.tree().root_node.base;
}
pub fn clone(self: *AnalysisContext) !AnalysisContext {
// Create a new tree so it can be destroyed by the cloned AnalysisContext without affecting the original
const tree = try self.handle.tree(self.store.allocator);
pub fn clone(self: *AnalysisContext) AnalysisContext {
return AnalysisContext{
.store = self.store,
.handle = self.handle,
.arena = self.arena,
.tree = tree,
.scope_nodes = self.scope_nodes,
.in_container = self.in_container,
.std_uri = self.std_uri,
};
}
pub fn deinit(self: *AnalysisContext) void {
self.tree.deinit();
}
};
pub fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 {
@ -399,17 +385,14 @@ pub fn analysisContext(
position: usize,
zig_lib_path: ?[]const u8,
) !AnalysisContext {
const tree = try handle.tree(self.allocator);
var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&arena.allocator);
const in_container = try analysis.declsFromIndex(arena, &scope_nodes, tree, position);
const in_container = try analysis.declsFromIndex(arena, &scope_nodes, handle.tree, position);
const std_uri = try stdUriFromLibPath(&arena.allocator, zig_lib_path);
return AnalysisContext{
.store = self,
.handle = handle,
.arena = arena,
.tree = tree,
.scope_nodes = scope_nodes.items,
.in_container = in_container,
.std_uri = std_uri,

View File

@ -91,8 +91,7 @@ fn astLocationToRange(loc: std.zig.ast.Tree.Location) types.Range {
}
fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void {
const tree = try handle.tree(allocator);
defer tree.deinit();
const tree = handle.tree;
// Use an arena for our local memory allocations.
var arena = std.heap.ArenaAllocator.init(allocator);
@ -179,7 +178,7 @@ fn containerToCompletion(
var child_idx: usize = 0;
while (container.iterate(child_idx)) |child_node| : (child_idx += 1) {
// Declarations in the same file do not need to be public.
if (orig_handle == analysis_ctx.handle or analysis.isNodePublic(analysis_ctx.tree, child_node)) {
if (orig_handle == analysis_ctx.handle or analysis.isNodePublic(analysis_ctx.tree(), child_node)) {
try nodeToCompletion(list, analysis_ctx, orig_handle, child_node, config);
}
}
@ -192,7 +191,7 @@ fn nodeToCompletion(
node: *std.zig.ast.Node,
config: Config,
) error{OutOfMemory}!void {
var doc = if (try analysis.getDocComments(list.allocator, analysis_ctx.tree, node)) |doc_comments|
var doc = if (try analysis.getDocComments(list.allocator, analysis_ctx.tree(), node)) |doc_comments|
types.MarkupContent{
.kind = .Markdown,
.value = doc_comments,
@ -208,17 +207,17 @@ fn nodeToCompletion(
const func = node.cast(std.zig.ast.Node.FnProto).?;
if (func.name_token) |name_token| {
const insert_text = if (config.enable_snippets)
try analysis.getFunctionSnippet(list.allocator, analysis_ctx.tree, func)
try analysis.getFunctionSnippet(list.allocator, analysis_ctx.tree(), func)
else
null;
const is_type_function = analysis.isTypeFunction(analysis_ctx.tree, func);
const is_type_function = analysis.isTypeFunction(analysis_ctx.tree(), func);
try list.append(.{
.label = analysis_ctx.tree.tokenSlice(name_token),
.label = analysis_ctx.tree().tokenSlice(name_token),
.kind = if (is_type_function) .Struct else .Function,
.documentation = doc,
.detail = analysis.getFunctionSignature(analysis_ctx.tree, func),
.detail = analysis.getFunctionSignature(analysis_ctx.tree(), func),
.insertText = insert_text,
.insertTextFormat = if (config.enable_snippets) .Snippet else .PlainText,
});
@ -226,14 +225,13 @@ fn nodeToCompletion(
},
.VarDecl => {
const var_decl = node.cast(std.zig.ast.Node.VarDecl).?;
const is_const = analysis_ctx.tree.token_ids[var_decl.mut_token] == .Keyword_const;
const is_const = analysis_ctx.tree().token_ids[var_decl.mut_token] == .Keyword_const;
var child_analysis_context = try analysis_ctx.clone();
defer child_analysis_context.deinit();
var child_analysis_context = analysis_ctx.clone();
const child_node = block: {
if (var_decl.type_node) |type_node| {
if (std.mem.eql(u8, "type", analysis_ctx.tree.tokenSlice(type_node.firstToken()))) {
if (std.mem.eql(u8, "type", analysis_ctx.tree().tokenSlice(type_node.firstToken()))) {
break :block var_decl.init_node orelse type_node;
}
break :block type_node;
@ -250,10 +248,10 @@ fn nodeToCompletion(
}
}
try list.append(.{
.label = analysis_ctx.tree.tokenSlice(var_decl.name_token),
.label = analysis_ctx.tree().tokenSlice(var_decl.name_token),
.kind = if (is_const) .Constant else .Variable,
.documentation = doc,
.detail = analysis.getVariableSignature(analysis_ctx.tree, var_decl),
.detail = analysis.getVariableSignature(analysis_ctx.tree(), var_decl),
});
},
.PrefixOp => {
@ -272,7 +270,7 @@ fn nodeToCompletion(
.kind = .Field,
});
},
else => if (analysis.nodeToString(analysis_ctx.tree, node)) |string| {
else => if (analysis.nodeToString(analysis_ctx.tree(), node)) |string| {
try list.append(.{
.label = string,
.kind = .Field,
@ -302,8 +300,7 @@ fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []cons
}
fn gotoDefinitionGlobal(id: i64, pos_index: usize, handle: DocumentStore.Handle) !void {
var tree = try handle.tree(allocator);
defer tree.deinit();
const tree = handle.tree;
const name = identifierFromPosition(pos_index, handle);
if (name.len == 0) return try respondGeneric(id, null_result_response);
@ -343,7 +340,6 @@ fn gotoDefinitionFieldAccess(
defer arena.deinit();
var analysis_ctx = try document_store.analysisContext(handle, &arena, try handle.document.positionToIndex(position), config.zig_lib_path);
defer analysis_ctx.deinit();
const line = try handle.document.getLine(@intCast(usize, position.line));
var tokenizer = std.zig.Tokenizer.init(line[line_start_idx..]);
@ -352,14 +348,14 @@ fn gotoDefinitionFieldAccess(
name = try std.mem.dupe(&arena.allocator, u8, name);
if (analysis.getFieldAccessTypeNode(&analysis_ctx, &tokenizer, line_length)) |container| {
const decl = analysis.getChild(analysis_ctx.tree, container, name) orelse return try respondGeneric(id, null_result_response);
const name_token = analysis.getDeclNameToken(analysis_ctx.tree, decl) orelse unreachable;
const decl = analysis.getChild(analysis_ctx.tree(), container, name) orelse return try respondGeneric(id, null_result_response);
const name_token = analysis.getDeclNameToken(analysis_ctx.tree(), decl) orelse unreachable;
return try send(types.Response{
.id = .{ .Integer = id },
.result = .{
.Location = .{
.uri = analysis_ctx.handle.document.uri,
.range = astLocationToRange(analysis_ctx.tree.tokenLocation(0, name_token)),
.range = astLocationToRange(analysis_ctx.tree().tokenLocation(0, name_token)),
},
},
});
@ -369,8 +365,7 @@ fn gotoDefinitionFieldAccess(
}
fn gotoDefinitionString(id: i64, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void {
var tree = try handle.tree(allocator);
defer tree.deinit();
const tree = handle.tree;
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
@ -405,8 +400,6 @@ fn completeGlobal(id: i64, pos_index: usize, handle: *DocumentStore.Handle, conf
defer arena.deinit();
var analysis_ctx = try document_store.analysisContext(handle, &arena, pos_index, config.zig_lib_path);
defer analysis_ctx.deinit();
for (analysis_ctx.scope_nodes) |decl_ptr| {
var decl = decl_ptr.*;
try nodeToCompletion(&completions, &analysis_ctx, handle, decl_ptr, config);
@ -428,8 +421,6 @@ fn completeFieldAccess(id: i64, handle: *DocumentStore.Handle, position: types.P
defer arena.deinit();
var analysis_ctx = try document_store.analysisContext(handle, &arena, try handle.document.positionToIndex(position), config.zig_lib_path);
defer analysis_ctx.deinit();
var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator);
const line = try handle.document.getLine(@intCast(usize, position.line));