diff --git a/src/analysis.zig b/src/analysis.zig index f6fe52f..749357b 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -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; diff --git a/src/document_store.zig b/src/document_store.zig index a808c73..489baff 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -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, diff --git a/src/main.zig b/src/main.zig index f4d861c..76a1dc3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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));