From 0a9cf875855b1480e363810b32606440cffa60e8 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 16:12:00 +0300 Subject: [PATCH 01/14] Added symbol tables --- src/analysis.zig | 402 ++++++++++++++++++++++++++++++++++++++++- src/document_store.zig | 15 +- 2 files changed, 413 insertions(+), 4 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 3283cc0..5110c75 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1013,9 +1013,7 @@ pub fn declsFromIndexInternal( } } }, - .Use => { - - }, + .Use => {}, else => {}, } } @@ -1288,3 +1286,401 @@ pub fn getDocumentSymbols(allocator: *std.mem.Allocator, tree: *ast.Tree) ![]typ return symbols.items; } + +const DocumentStore = @import("document_store.zig"); + +// TODO Will I need to store a handle with the nodes? +pub const Declaration = union(enum) { + ast_node: *ast.Node, + param_decl: *ast.Node.FnProto.ParamDecl, + pointer_payload: struct { + node: *ast.Node.PointerPayload, + condition: *ast.Node, + }, + array_payload: struct { + identifier: *ast.Node, + array_expr: *ast.Node, + }, + switch_payload: struct { + node: *ast.Node.PointerPayload, + items: []const *ast.Node, + }, +}; + +pub const DocumentScope = struct { + scopes: []const Scope, + + // TODO Methods + + // TODO When looking up a symbol, have an accept_field argument. + + pub fn debugPrint(self: DocumentScope) void { + for (self.scopes) |scope| { + std.debug.warn( + \\-------------------------- + \\Scope {}, range: [{}, {}) + \\ {} usingnamespaces + \\Decls: + , .{ + scope.data, + scope.range.start, + scope.range.end, + scope.uses.len, + }); + + var decl_it = scope.decls.iterator(); + var idx: usize = 0; + while (decl_it.next()) |name_decl| : (idx += 1) { + if (idx != 0) std.debug.warn(", ", .{}); + std.debug.warn("{}", .{name_decl.key}); + } + std.debug.warn("\n--------------------------\n", .{}); + } + } + + pub fn deinit(self: DocumentScope, allocator: *std.mem.Allocator) void { + for (self.scopes) |scope| { + scope.decls.deinit(); + allocator.free(scope.uses); + } + allocator.free(self.scopes); + } +}; + +pub const Scope = struct { + pub const Data = union(enum) { + container: *ast.Node, // .id is ContainerDecl or Root + function: *ast.Node, // .id is FnProto + block: *ast.Node, // .id is Block + other, + }; + + range: SourceRange, + decls: std.StringHashMap(Declaration), + uses: []const *ast.Node.Use, + + data: Data, + + // TODO Methods +}; + +pub fn makeDocumentScope(allocator: *std.mem.Allocator, tree: *ast.Tree) !DocumentScope { + var scopes = std.ArrayList(Scope).init(allocator); + errdefer scopes.deinit(); + + try makeScopeInternal(allocator, &scopes, tree, &tree.root_node.base); + return DocumentScope{ + .scopes = scopes.toOwnedSlice(), + }; +} + +fn nodeSourceRange(tree: *ast.Tree, node: *ast.Node) SourceRange { + return SourceRange{ + .start = tree.token_locs[node.firstToken()].start, + .end = tree.token_locs[node.lastToken()].end, + }; +} + +fn makeScopeInternal( + allocator: *std.mem.Allocator, + scopes: *std.ArrayList(Scope), + tree: *ast.Tree, + node: *ast.Node, +) error{OutOfMemory}!void { + if (node.id == .Root or node.id == .ContainerDecl) { + const ast_decls = switch (node.id) { + .ContainerDecl => node.cast(ast.Node.ContainerDecl).?.fieldsAndDeclsConst(), + .Root => node.cast(ast.Node.Root).?.declsConst(), + else => unreachable, + }; + + (try scopes.addOne()).* = .{ + .range = nodeSourceRange(tree, node), + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .{ .container = node }, + }; + var scope_idx = scopes.items.len - 1; + var uses = std.ArrayList(*ast.Node.Use).init(allocator); + + errdefer { + scopes.items[scope_idx].decls.deinit(); + uses.deinit(); + } + + for (ast_decls) |decl| { + if (decl.cast(ast.Node.Use)) |use| { + try uses.append(use); + continue; + } + + try makeScopeInternal(allocator, scopes, tree, decl); + const name = getDeclName(tree, decl) orelse continue; + if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = decl })) |existing| { + // TODO Record a redefinition error. + } + } + + scopes.items[scope_idx].uses = uses.toOwnedSlice(); + return; + } + + switch (node.id) { + .FnProto => { + const func = node.cast(ast.Node.FnProto).?; + + (try scopes.addOne()).* = .{ + .range = nodeSourceRange(tree, node), + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .{ .function = node }, + }; + var scope_idx = scopes.items.len - 1; + errdefer scopes.items[scope_idx].decls.deinit(); + + for (func.params()) |*param| { + if (param.name_token) |name_tok| { + if (try scopes.items[scope_idx].decls.put(tree.tokenSlice(name_tok), .{ .param_decl = param })) |existing| { + // TODO Record a redefinition error + } + } + } + + if (func.body_node) |body| { + try makeScopeInternal(allocator, scopes, tree, body); + } + + return; + }, + .TestDecl => { + return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.TestDecl).?.body_node); + }, + .Block => { + (try scopes.addOne()).* = .{ + .range = nodeSourceRange(tree, node), + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .{ .block = node }, + }; + var scope_idx = scopes.items.len - 1; + var uses = std.ArrayList(*ast.Node.Use).init(allocator); + + errdefer { + scopes.items[scope_idx].decls.deinit(); + uses.deinit(); + } + + var child_idx: usize = 0; + while (node.iterate(child_idx)) |child_node| : (child_idx += 1) { + if (child_node.cast(ast.Node.Use)) |use| { + try uses.append(use); + continue; + } + + try makeScopeInternal(allocator, scopes, tree, child_node); + if (child_node.cast(ast.Node.VarDecl)) |var_decl| { + const name = tree.tokenSlice(var_decl.name_token); + if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = child_node })) |existing| { + // TODO Record a redefinition error. + } + } + } + + scopes.items[scope_idx].uses = uses.toOwnedSlice(); + return; + }, + .Comptime => { + return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.Comptime).?.expr); + }, + .If => { + const if_node = node.cast(ast.Node.If).?; + + if (if_node.payload) |payload| { + std.debug.assert(payload.id == .PointerPayload); + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[payload.firstToken()].start, + .end = tree.token_locs[if_node.body.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const ptr_payload = payload.cast(ast.Node.PointerPayload).?; + std.debug.assert(ptr_payload.value_symbol.id == .Identifier); + const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken()); + try scope.decls.putNoClobber(name, .{ + .pointer_payload = .{ + .node = ptr_payload, + .condition = if_node.condition, + }, + }); + } + try makeScopeInternal(allocator, scopes, tree, if_node.body); + + if (if_node.@"else") |else_node| { + if (else_node.payload) |payload| { + std.debug.assert(payload.id == .Payload); + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[payload.firstToken()].start, + .end = tree.token_locs[else_node.body.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const err_payload = payload.cast(ast.Node.Payload).?; + std.debug.assert(err_payload.error_symbol.id == .Identifier); + const name = tree.tokenSlice(err_payload.error_symbol.firstToken()); + try scope.decls.putNoClobber(name, .{ .ast_node = payload }); + } + try makeScopeInternal(allocator, scopes, tree, else_node.body); + } + }, + .While => { + const while_node = node.cast(ast.Node.While).?; + if (while_node.payload) |payload| { + std.debug.assert(payload.id == .PointerPayload); + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[payload.firstToken()].start, + .end = tree.token_locs[while_node.body.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const ptr_payload = payload.cast(ast.Node.PointerPayload).?; + std.debug.assert(ptr_payload.value_symbol.id == .Identifier); + const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken()); + try scope.decls.putNoClobber(name, .{ + .pointer_payload = .{ + .node = ptr_payload, + .condition = while_node.condition, + }, + }); + } + try makeScopeInternal(allocator, scopes, tree, while_node.body); + + if (while_node.@"else") |else_node| { + if (else_node.payload) |payload| { + std.debug.assert(payload.id == .Payload); + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[payload.firstToken()].start, + .end = tree.token_locs[else_node.body.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const err_payload = payload.cast(ast.Node.Payload).?; + std.debug.assert(err_payload.error_symbol.id == .Identifier); + const name = tree.tokenSlice(err_payload.error_symbol.firstToken()); + try scope.decls.putNoClobber(name, .{ .ast_node = payload }); + } + try makeScopeInternal(allocator, scopes, tree, else_node.body); + } + }, + .For => { + const for_node = node.cast(ast.Node.For).?; + std.debug.assert(for_node.payload.id == .PointerIndexPayload); + const ptr_idx_payload = for_node.payload.cast(ast.Node.PointerIndexPayload).?; + std.debug.assert(ptr_idx_payload.value_symbol.id == .Identifier); + + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[ptr_idx_payload.firstToken()].start, + .end = tree.token_locs[for_node.body.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const value_name = tree.tokenSlice(ptr_idx_payload.value_symbol.firstToken()); + try scope.decls.putNoClobber(value_name, .{ + .array_payload = .{ + .identifier = ptr_idx_payload.value_symbol, + .array_expr = for_node.array_expr, + }, + }); + + if (ptr_idx_payload.index_symbol) |index_symbol| { + std.debug.assert(index_symbol.id == .Identifier); + const index_name = tree.tokenSlice(index_symbol.firstToken()); + if (try scope.decls.put(index_name, .{ .ast_node = index_symbol })) |existing| { + // TODO Record a redefinition error + } + } + + try makeScopeInternal(allocator, scopes, tree, for_node.body); + if (for_node.@"else") |else_node| { + std.debug.assert(else_node.payload == null); + try makeScopeInternal(allocator, scopes, tree, else_node.body); + } + }, + .Switch => { + const switch_node = node.cast(ast.Node.Switch).?; + for (switch_node.casesConst()) |case| { + if (case.*.cast(ast.Node.SwitchCase)) |case_node| { + if (case_node.payload) |payload| { + std.debug.assert(payload.id == .PointerPayload); + var scope = try scopes.addOne(); + scope.* = .{ + .range = .{ + .start = tree.token_locs[payload.firstToken()].start, + .end = tree.token_locs[case_node.expr.lastToken()].end, + }, + .decls = std.StringHashMap(Declaration).init(allocator), + .uses = &[0]*ast.Node.Use{}, + .data = .other, + }; + errdefer scope.decls.deinit(); + + const ptr_payload = payload.cast(ast.Node.PointerPayload).?; + std.debug.assert(ptr_payload.value_symbol.id == .Identifier); + const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken()); + try scope.decls.putNoClobber(name, .{ + .switch_payload = .{ + .node = ptr_payload, + .items = case_node.itemsConst(), + }, + }); + } + try makeScopeInternal(allocator, scopes, tree, case_node.expr); + } + } + }, + .VarDecl => { + const var_decl = node.cast(ast.Node.VarDecl).?; + if (var_decl.type_node) |type_node| { + try makeScopeInternal(allocator, scopes, tree, type_node); + } + if (var_decl.init_node) |init_node| { + try makeScopeInternal(allocator, scopes, tree, init_node); + } + }, + else => { + var child_idx: usize = 0; + while (node.iterate(child_idx)) |child_node| : (child_idx += 1) { + try makeScopeInternal(allocator, scopes, tree, child_node); + } + }, + } +} diff --git a/src/document_store.zig b/src/document_store.zig index 7c7cf0c..b657d20 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -21,6 +21,7 @@ pub const Handle = struct { count: usize, import_uris: std.ArrayList([]const u8), tree: *std.zig.ast.Tree, + document_scope: analysis.DocumentScope, associated_build_file: ?*BuildFile, is_build_file: ?*BuildFile, @@ -191,6 +192,12 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand var handle = try self.allocator.create(Handle); errdefer self.allocator.destroy(handle); + const tree = try std.zig.parse(self.allocator, text); + errdefer tree.deinit(); + + const document_scope = try analysis.makeDocumentScope(self.allocator, tree); + errdefer document_scope.deinit(self.allocator); + handle.* = Handle{ .count = 1, .import_uris = std.ArrayList([]const u8).init(self.allocator), @@ -199,7 +206,8 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand .text = text, .mem = text, }, - .tree = try std.zig.parse(self.allocator, text), + .tree = tree, + .document_scope = document_scope, .associated_build_file = null, .is_build_file = null, }; @@ -362,6 +370,9 @@ fn refreshDocument(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const handle.tree.deinit(); handle.tree = try std.zig.parse(self.allocator, handle.document.text); + handle.document_scope.deinit(self.allocator); + handle.document_scope = try analysis.makeDocumentScope(self.allocator, handle.tree); + // 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, {}) ? @@ -700,6 +711,8 @@ pub fn deinit(self: *DocumentStore) void { entry.value.import_uris.deinit(); self.allocator.free(entry.key); self.allocator.destroy(entry.value); + + entry.value.document_scope.deinit(self.allocator); } self.handles.deinit(); From 9b08d9e88a1c70881a0bbe9ce8f26305b5f30f47 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 19:01:44 +0300 Subject: [PATCH 02/14] Started removing scope_nodes, using symbol table instead --- src/analysis.zig | 339 ++++++++++++++--------------------------- src/document_store.zig | 45 ------ src/main.zig | 82 ++++++---- 3 files changed, 170 insertions(+), 296 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 5110c75..f1b06e0 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -3,23 +3,6 @@ const AnalysisContext = @import("document_store.zig").AnalysisContext; const ast = std.zig.ast; const types = @import("types.zig"); -/// REALLY BAD CODE, PLEASE DON'T USE THIS!!!!!!! (only for testing) -pub fn getFunctionByName(tree: *ast.Tree, name: []const u8) ?*ast.Node.FnProto { - var decls = tree.root_node.decls.iterator(0); - while (decls.next()) |decl_ptr| { - var decl = decl_ptr.*; - switch (decl.id) { - .FnProto => { - const func = decl.cast(ast.Node.FnProto).?; - if (std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return func; - }, - else => {}, - } - } - - return null; -} - /// Get a declaration's doc comment node fn getDocCommentNode(tree: *ast.Tree, node: *ast.Node) ?*ast.Node.DocComment { if (node.cast(ast.Node.FnProto)) |func| { @@ -836,213 +819,12 @@ fn makeVarDeclNode( return &var_decl_node.base; } -pub fn declsFromIndexInternal( - arena: *std.heap.ArenaAllocator, - decls: *std.ArrayList(*ast.Node), - tree: *ast.Tree, - node: *ast.Node, - source_index: usize, - innermost_container: **ast.Node, -) error{OutOfMemory}!void { - switch (node.id) { - .Root, .ContainerDecl => { - innermost_container.* = node; - var node_idx: usize = 0; - while (node.iterate(node_idx)) |child_node| : (node_idx += 1) { - // Skip over container fields, we can only dot access those. - if (child_node.id == .ContainerField) continue; - - const is_contained = nodeContainsSourceIndex(tree, child_node, source_index); - // If the cursor is in a variable decls it will insert itself anyway, we don't need to take care of it. - if ((is_contained and child_node.id != .VarDecl) or !is_contained) try decls.append(child_node); - if (is_contained) { - try declsFromIndexInternal(arena, decls, tree, child_node, source_index, innermost_container); - } - } - }, - .FnProto => { - const func = node.cast(ast.Node.FnProto).?; - - // TODO: This is a hack to enable param decls with the new parser - for (func.paramsConst()) |param| { - if (param.param_type != .type_expr or param.name_token == null) continue; - - try decls.append(try makeVarDeclNode( - &arena.allocator, - param.doc_comments, - param.comptime_token, - param.name_token.?, - param.param_type.type_expr, - null, - )); - } - - if (func.body_node) |body_node| { - if (!nodeContainsSourceIndex(tree, body_node, source_index)) return; - try declsFromIndexInternal(arena, decls, tree, body_node, source_index, innermost_container); - } - }, - .TestDecl => { - const test_decl = node.cast(ast.Node.TestDecl).?; - if (!nodeContainsSourceIndex(tree, test_decl.body_node, source_index)) return; - try declsFromIndexInternal(arena, decls, tree, test_decl.body_node, source_index, innermost_container); - }, - .Block => { - var inode_idx: usize = 0; - while (node.iterate(inode_idx)) |inode| : (inode_idx += 1) { - if (nodeComesAfterSourceIndex(tree, inode, source_index)) return; - try declsFromIndexInternal(arena, decls, tree, inode, source_index, innermost_container); - } - }, - .Comptime => { - const comptime_stmt = node.cast(ast.Node.Comptime).?; - if (nodeComesAfterSourceIndex(tree, comptime_stmt.expr, source_index)) return; - try declsFromIndexInternal(arena, decls, tree, comptime_stmt.expr, source_index, innermost_container); - }, - .If => { - const if_node = node.cast(ast.Node.If).?; - if (nodeContainsSourceIndex(tree, if_node.body, source_index)) { - if (if_node.payload) |payload| { - std.debug.assert(payload.id == .PointerPayload); - const ptr_payload = payload.cast(ast.Node.PointerPayload).?; - std.debug.assert(ptr_payload.value_symbol.id == .Identifier); - - try decls.append(try makeVarDeclNode( - &arena.allocator, - null, - null, - ptr_payload.value_symbol.firstToken(), - null, - try makeUnwrapNode(&arena.allocator, if_node.condition), - )); - } - return try declsFromIndexInternal(arena, decls, tree, if_node.body, source_index, innermost_container); - } - - if (if_node.@"else") |else_node| { - if (nodeContainsSourceIndex(tree, else_node.body, source_index)) { - if (else_node.payload) |payload| { - try declsFromIndexInternal(arena, decls, tree, payload, source_index, innermost_container); - } - return try declsFromIndexInternal(arena, decls, tree, else_node.body, source_index, innermost_container); - } - } - }, - .While => { - const while_node = node.cast(ast.Node.While).?; - if (nodeContainsSourceIndex(tree, while_node.body, source_index)) { - if (while_node.payload) |payload| { - std.debug.assert(payload.id == .PointerPayload); - const ptr_payload = payload.cast(ast.Node.PointerPayload).?; - std.debug.assert(ptr_payload.value_symbol.id == .Identifier); - - try decls.append(try makeVarDeclNode( - &arena.allocator, - null, - null, - ptr_payload.value_symbol.firstToken(), - null, - try makeUnwrapNode(&arena.allocator, while_node.condition), - )); - } - return try declsFromIndexInternal(arena, decls, tree, while_node.body, source_index, innermost_container); - } - - if (while_node.@"else") |else_node| { - if (nodeContainsSourceIndex(tree, else_node.body, source_index)) { - if (else_node.payload) |payload| { - try declsFromIndexInternal(arena, decls, tree, payload, source_index, innermost_container); - } - return try declsFromIndexInternal(arena, decls, tree, else_node.body, source_index, innermost_container); - } - } - }, - .For => { - const for_node = node.cast(ast.Node.For).?; - if (nodeContainsSourceIndex(tree, for_node.body, source_index)) { - std.debug.assert(for_node.payload.id == .PointerIndexPayload); - const ptr_idx_payload = for_node.payload.cast(ast.Node.PointerIndexPayload).?; - std.debug.assert(ptr_idx_payload.value_symbol.id == .Identifier); - try decls.append(try makeVarDeclNode( - &arena.allocator, - null, - null, - ptr_idx_payload.value_symbol.firstToken(), - null, - try makeAccessNode(&arena.allocator, for_node.array_expr), - )); - - if (ptr_idx_payload.index_symbol) |idx| { - try decls.append(idx); - } - - return try declsFromIndexInternal(arena, decls, tree, for_node.body, source_index, innermost_container); - } - - if (for_node.@"else") |else_node| { - if (nodeContainsSourceIndex(tree, else_node.body, source_index)) { - if (else_node.payload) |payload| { - try declsFromIndexInternal(arena, decls, tree, payload, source_index, innermost_container); - } - return try declsFromIndexInternal(arena, decls, tree, else_node.body, source_index, innermost_container); - } - } - }, - .Switch => { - const switch_node = node.cast(ast.Node.Switch).?; - for (switch_node.casesConst()) |case| { - const case_node = case.*.cast(ast.Node.SwitchCase).?; - if (nodeContainsSourceIndex(tree, case_node.expr, source_index)) { - if (case_node.payload) |payload| { - try declsFromIndexInternal(arena, decls, tree, payload, source_index, innermost_container); - } - return try declsFromIndexInternal(arena, decls, tree, case_node.expr, source_index, innermost_container); - } - } - }, - // TODO: These convey no type information... - .Payload => try decls.append(node.cast(ast.Node.Payload).?.error_symbol), - .PointerPayload => try decls.append(node.cast(ast.Node.PointerPayload).?.value_symbol), - // This should be completely handled for the .For code. - .PointerIndexPayload => unreachable, - .VarDecl => { - try decls.append(node); - if (node.cast(ast.Node.VarDecl).?.init_node) |child| { - if (nodeContainsSourceIndex(tree, child, source_index)) { - try declsFromIndexInternal(arena, decls, tree, child, source_index, innermost_container); - } - } - }, - .Use => {}, - else => {}, - } -} - -pub fn addChildrenNodes(decls: *std.ArrayList(*ast.Node), tree: *ast.Tree, node: *ast.Node) !void { - var node_idx: usize = 0; - while (node.iterate(node_idx)) |child_node| : (node_idx += 1) { - try decls.append(child_node); - } -} - -pub fn declsFromIndex(arena: *std.heap.ArenaAllocator, decls: *std.ArrayList(*ast.Node), tree: *ast.Tree, source_index: usize) !*ast.Node { - var innermost_container = &tree.root_node.base; - try declsFromIndexInternal(arena, decls, tree, &tree.root_node.base, source_index, &innermost_container); - return innermost_container; -} - fn nodeContainsSourceIndex(tree: *ast.Tree, node: *ast.Node, source_index: usize) bool { const first_token = tree.token_locs[node.firstToken()]; const last_token = tree.token_locs[node.lastToken()]; return source_index >= first_token.start and source_index <= last_token.end; } -fn nodeComesAfterSourceIndex(tree: *ast.Tree, node: *ast.Node, source_index: usize) bool { - const first_token = tree.token_locs[node.firstToken()]; - const last_token = tree.token_locs[node.lastToken()]; - return source_index < first_token.start; -} - pub fn getImportStr(tree: *ast.Tree, source_index: usize) ?[]const u8 { var node = &tree.root_node.base; @@ -1289,7 +1071,6 @@ pub fn getDocumentSymbols(allocator: *std.mem.Allocator, tree: *ast.Tree) ![]typ const DocumentStore = @import("document_store.zig"); -// TODO Will I need to store a handle with the nodes? pub const Declaration = union(enum) { ast_node: *ast.Node, param_decl: *ast.Node.FnProto.ParamDecl, @@ -1307,13 +1088,110 @@ pub const Declaration = union(enum) { }, }; +pub const DeclWithHandle = struct { + decl: *Declaration, + handle: *DocumentStore.Handle, + + pub fn location(self: DeclWithHandle) ast.Tree.Location { + const tree = self.handle.tree; + return switch (self.decl.*) { + .ast_node => |n| block: { + const name_token = getDeclNameToken(tree, n).?; + break :block tree.tokenLocation(0, name_token); + }, + .param_decl => |p| tree.tokenLocation(0, p.name_token.?), + .pointer_payload => |pp| tree.tokenLocation(0, pp.node.value_symbol.firstToken()), + .array_payload => |ap| tree.tokenLocation(0, ap.identifier.firstToken()), + .switch_payload => |sp| tree.tokenLocation(0, sp.node.value_symbol.firstToken()), + }; + } +}; + +pub fn iterateSymbolsGlobal( + store: *DocumentStore, + handle: *DocumentStore.Handle, + source_index: usize, + comptime callback: var, + context: var, +) !void { + for (handle.document_scope.scopes) |scope| { + if (source_index >= scope.range.start and source_index < scope.range.end) { + var decl_it = scope.decls.iterator(); + while (decl_it.next()) |entry| { + try callback(context, DeclWithHandle{ .decl = &entry.value, .handle = handle }); + } + + for (scope.uses) |use| { + // @TODO Resolve uses, iterate over their symbols. + } + } + + if (scope.range.start >= source_index) return; + } +} + +pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, symbol: []const u8, source_index: usize) !?DeclWithHandle { + for (handle.document_scope.scopes) |scope| { + if (source_index >= scope.range.start and source_index < scope.range.end) { + if (scope.decls.get(symbol)) |candidate| { + switch (candidate.value) { + .ast_node => |node| { + if (node.id == .ContainerField) continue; + }, + else => {}, + } + return DeclWithHandle{ + .decl = &candidate.value, + .handle = handle, + }; + } + + for (scope.uses) |use| { + // @TODO Resolve use, lookup symbol in resulting scope. + } + } + + if (scope.range.start >= source_index) return null; + } + + return null; +} + +pub fn lookupSymbolContainer(store: *DocumentScope, handle: *DocumentStore.Handle, container: *ast.Node, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { + std.debug.assert(container.id == .ContainerDecl or container.id == .Root); + // Find the container scope. + var maybe_container_scope: ?*Scope = null; + for (handle.document_scope.scopes) |*scope| { + switch (scope.*) { + .container => |node| if (node == container) { + maybe_container_scope = scope; + break; + }, + } + } + + if (maybe_container_scope) |container_scope| { + if (container_scope.decls.get(symbol)) |candidate| { + switch (candidate.value) { + .ast_node => |node| { + if (node.id == .ContainerDecl and !accept_fields) return null; + }, + else => {}, + } + return &candidate.value; + } + + for (container_scope.uses) |use| { + // @TODO Resolve use, lookup symbol in resulting scope. + } + return null; + } + unreachable; +} + pub const DocumentScope = struct { scopes: []const Scope, - // TODO Methods - - // TODO When looking up a symbol, have an accept_field argument. - pub fn debugPrint(self: DocumentScope) void { for (self.scopes) |scope| { std.debug.warn( @@ -1357,11 +1235,10 @@ pub const Scope = struct { range: SourceRange, decls: std.StringHashMap(Declaration), + tests: []const *ast.Node, uses: []const *ast.Node.Use, data: Data, - - // TODO Methods }; pub fn makeDocumentScope(allocator: *std.mem.Allocator, tree: *ast.Tree) !DocumentScope { @@ -1381,6 +1258,10 @@ fn nodeSourceRange(tree: *ast.Tree, node: *ast.Node) SourceRange { }; } +// TODO Make enum and error stores per-document +// CLear the doc ones before calling this and +// rebuild them here. + fn makeScopeInternal( allocator: *std.mem.Allocator, scopes: *std.ArrayList(Scope), @@ -1398,14 +1279,17 @@ fn makeScopeInternal( .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .{ .container = node }, }; var scope_idx = scopes.items.len - 1; var uses = std.ArrayList(*ast.Node.Use).init(allocator); + var tests = std.ArrayList(*ast.Node).init(allocator); errdefer { scopes.items[scope_idx].decls.deinit(); uses.deinit(); + tests.deinit(); } for (ast_decls) |decl| { @@ -1416,6 +1300,11 @@ fn makeScopeInternal( try makeScopeInternal(allocator, scopes, tree, decl); const name = getDeclName(tree, decl) orelse continue; + if (decl.id == .TestDecl) { + try tests.append(decl); + continue; + } + if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = decl })) |existing| { // TODO Record a redefinition error. } diff --git a/src/document_store.zig b/src/document_store.zig index b657d20..4fe1429 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -540,8 +540,6 @@ pub const AnalysisContext = struct { // This arena is used for temporary allocations while analyzing, // not for the tree allocations. arena: *std.heap.ArenaAllocator, - scope_nodes: []*std.zig.ast.Node, - in_container: *std.zig.ast.Node, std_uri: ?[]const u8, error_completions: *TagStore, enum_completions: *TagStore, @@ -550,25 +548,6 @@ pub const AnalysisContext = struct { 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); - self.scope_nodes = scope_nodes.items; - self.in_container = &self.tree().root_node.base; - } - - pub fn onContainer(self: *AnalysisContext, container: *std.zig.ast.Node) !void { - std.debug.assert(container.id == .ContainerDecl or container.id == .Root); - - if (self.in_container != container) { - self.in_container = container; - - var scope_nodes = std.ArrayList(*std.zig.ast.Node).fromOwnedSlice(&self.arena.allocator, self.scope_nodes); - try analysis.addChildrenNodes(&scope_nodes, self.tree(), container); - self.scope_nodes = scope_nodes.items; - } - } - pub fn onImport(self: *AnalysisContext, import_str: []const u8) !?*std.zig.ast.Node { const allocator = self.store.allocator; const final_uri = (try uriFromImportStr( @@ -588,7 +567,6 @@ 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; - try self.refreshScopeNodes(); return &self.tree().root_node.base; } } @@ -603,7 +581,6 @@ pub const AnalysisContext = struct { new_handle.count += 1; self.handle = new_handle; - try self.refreshScopeNodes(); return &self.tree().root_node.base; } @@ -639,24 +616,8 @@ pub const AnalysisContext = struct { self.handle = try newDocument(self.store, duped_final_uri, file_contents); } - try self.refreshScopeNodes(); return &self.tree().root_node.base; } - - pub fn clone(self: *AnalysisContext) !AnalysisContext { - // Copy the cope nodes, the rest are references - // that are not owned by the context. - return AnalysisContext{ - .store = self.store, - .handle = self.handle, - .arena = self.arena, - .scope_nodes = try std.mem.dupe(&self.arena.allocator, *std.zig.ast.Node, self.scope_nodes), - .in_container = self.in_container, - .std_uri = self.std_uri, - .error_completions = self.error_completions, - .enum_completions = self.enum_completions, - }; - } }; pub fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 { @@ -680,19 +641,13 @@ pub fn analysisContext( self: *DocumentStore, handle: *Handle, arena: *std.heap.ArenaAllocator, - position: usize, zig_lib_path: ?[]const u8, ) !AnalysisContext { - var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&arena.allocator); - 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, - .scope_nodes = scope_nodes.items, - .in_container = in_container, .std_uri = std_uri, .error_completions = &self.error_completions, .enum_completions = &self.enum_completions, diff --git a/src/main.zig b/src/main.zig index 3732310..095c820 100644 --- a/src/main.zig +++ b/src/main.zig @@ -218,8 +218,8 @@ const ResolveVarDeclFnAliasRewsult = struct { }; fn resolveVarDeclFnAlias(analysis_ctx: *DocumentStore.AnalysisContext, decl: *std.zig.ast.Node) !ResolveVarDeclFnAliasRewsult { - var child_analysis_context = try analysis_ctx.clone(); if (decl.cast(std.zig.ast.Node.VarDecl)) |var_decl| { + var child_analysis_context = analysis_ctx.*; const child_node = block: { if (var_decl.type_node) |type_node| { if (std.mem.eql(u8, "type", analysis_ctx.tree().tokenSlice(type_node.firstToken()))) { @@ -376,18 +376,26 @@ fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []cons return text[start_idx + 1 .. end_idx]; } -fn gotoDefinitionSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.AnalysisContext, decl: *std.zig.ast.Node) !void { - const result = try resolveVarDeclFnAlias(analysis_ctx, decl); +fn gotoDefinitionSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.AnalysisContext, decl_handle: analysis.DeclWithHandle) !void { + var handle = analysis_ctx.handle; - const name_token = analysis.getDeclNameToken(result.analysis_ctx.tree(), result.decl) orelse - return try respondGeneric(id, null_result_response); + const location = switch (decl_handle.decl.*) { + .ast_node => |node| block: { + const result = try resolveVarDeclFnAlias(analysis_ctx, node); + handle = result.analysis_ctx.handle; + const name_token = analysis.getDeclNameToken(handle.tree, result.decl) orelse + return try respondGeneric(id, null_result_response); + break :block handle.tree.tokenLocation(0, name_token); + }, + else => decl_handle.location(), + }; try send(types.Response{ .id = id, .result = .{ .Location = .{ - .uri = result.analysis_ctx.handle.document.uri, - .range = astLocationToRange(result.analysis_ctx.tree().tokenLocation(0, name_token)), + .uri = handle.document.uri, + .range = astLocationToRange(location), }, }, }); @@ -424,22 +432,20 @@ fn hoverSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.AnalysisContext }); } -fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: DocumentStore.Handle) !?*std.zig.ast.Node { - const name = identifierFromPosition(pos_index, handle); +fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: *DocumentStore.Handle) !?analysis.DeclWithHandle { + const name = identifierFromPosition(pos_index, handle.*); if (name.len == 0) return null; - var decl_nodes = std.ArrayList(*std.zig.ast.Node).init(&arena.allocator); - _ = try analysis.declsFromIndex(arena, &decl_nodes, handle.tree, pos_index); - - return analysis.getChildOfSlice(handle.tree, decl_nodes.items, name); + return try analysis.lookupSymbolGlobal(&document_store, handle, name, pos_index); } fn gotoDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - const decl = (try getSymbolGlobal(&arena, pos_index, handle.*)) orelse return try respondGeneric(id, null_result_response); - var analysis_ctx = try document_store.analysisContext(handle, &arena, pos_index, config.zig_lib_path); + const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); + + var analysis_ctx = try document_store.analysisContext(decl.handle, &arena, config.zig_lib_path); return try gotoDefinitionSymbol(id, &analysis_ctx, decl); } @@ -482,7 +488,7 @@ fn gotoDefinitionFieldAccess( var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, try handle.document.positionToIndex(position), config.zig_lib_path); + var analysis_ctx = try document_store.analysisContext(handle, &arena, config.zig_lib_path); const decl = (try getSymbolFieldAccess(&analysis_ctx, position, range, config)) orelse return try respondGeneric(id, null_result_response); return try gotoDefinitionSymbol(id, &analysis_ctx, decl); } @@ -530,6 +536,24 @@ fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *Document }); } +const DeclToCompletionContext = struct { + completions: *std.ArrayList(types.CompletionItem), + config: *const Config, + arena: *std.heap.ArenaAllocator, +}; + +fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { + switch (decl_handle.decl.*) { + .ast_node => |node| { + // @TODO Remove analysis context + var analysis_ctx = try document_store.analysisContext(decl_handle.handle, context.arena, context.config.zig_lib_path); + try nodeToCompletion(context.completions, &analysis_ctx, decl_handle.handle, node, context.config.*); + }, + else => {}, + // @TODO The rest + } +} + fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { // We use a local arena allocator to deallocate all temporary data without iterating var arena = std.heap.ArenaAllocator.init(allocator); @@ -537,16 +561,22 @@ fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore. // Deallocate all temporary data. defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, pos_index, config.zig_lib_path); - for (analysis_ctx.scope_nodes) |decl_ptr| { - var decl = decl_ptr.*; - if (decl.id == .Use) { - std.debug.warn("Found use!", .{}); - continue; - } + const context = DeclToCompletionContext{ + .completions = &completions, + .config = &config, + .arena = &arena, + }; + try analysis.iterateSymbolsGlobal(&document_store, handle, pos_index, decltoCompletion, context); - try nodeToCompletion(&completions, &analysis_ctx, handle, decl_ptr, config); - } + // for (analysis_ctx.scope_nodes) |decl_ptr| { + // var decl = decl_ptr.*; + // if (decl.id == .Use) { + // std.debug.warn("Found use!", .{}); + // continue; + // } + + // try nodeToCompletion(&completions, &analysis_ctx, handle, decl_ptr, config); + // } try send(types.Response{ .id = id, @@ -563,7 +593,7 @@ fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, posit var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, try handle.document.positionToIndex(position), config.zig_lib_path); + var analysis_ctx = try document_store.analysisContext(handle, &arena, config.zig_lib_path); var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); const line = try handle.document.getLine(@intCast(usize, position.line)); From d6609d918bae575d68725b0476bc257a1fcdc7a8 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 19:54:01 +0300 Subject: [PATCH 03/14] More progress --- src/analysis.zig | 168 ++++++++++++++++++++++------------------- src/document_store.zig | 154 +++++++++++++++---------------------- src/main.zig | 23 +----- 3 files changed, 157 insertions(+), 188 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index f1b06e0..a8f31ab 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const AnalysisContext = @import("document_store.zig").AnalysisContext; +const DocumentStore = @import("document_store.zig"); const ast = std.zig.ast; const types = @import("types.zig"); @@ -244,15 +244,14 @@ 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) { +fn resolveReturnType(store: *DocumentStore, fn_decl: *ast.Node.FnProto, handle: *DocumentStore.Handle) !?NodeWithHandle { + if (isTypeFunction(handle.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(handle.tree, fn_decl) orelse return null; if (ret.rhs) |rhs| - if (resolveTypeOfNode(analysis_ctx, rhs)) |res_rhs| switch (res_rhs.id) { + if (try resolveTypeOfNode(store, .{ .node = rhs, .handle = handle })) |res_rhs| switch (res_rhs.node.id) { .ContainerDecl => { - analysis_ctx.onContainer(res_rhs) catch return null; return res_rhs; }, else => return null, @@ -262,16 +261,16 @@ fn resolveReturnType(analysis_ctx: *AnalysisContext, fn_decl: *ast.Node.FnProto) } return switch (fn_decl.return_type) { - .Explicit, .InferErrorSet => |return_type| resolveTypeOfNode(analysis_ctx, return_type), + .Explicit, .InferErrorSet => |return_type| try resolveTypeOfNode(store, .{ .node = return_type, .handle = handle }), .Invalid => null, }; } /// Resolves the child type of an optional type -fn resolveUnwrapOptionalType(analysis_ctx: *AnalysisContext, opt: *ast.Node) ?*ast.Node { - if (opt.cast(ast.Node.PrefixOp)) |prefix_op| { +fn resolveUnwrapOptionalType(store: *DocumentStore, opt: NodeWithHandle) !?NodeWithHandle { + if (opt.node.cast(ast.Node.PrefixOp)) |prefix_op| { if (prefix_op.op == .OptionalType) { - return resolveTypeOfNode(analysis_ctx, prefix_op.rhs); + return try resolveTypeOfNode(store, .{ .node = prefix_op.rhs, .handle = opt.handle }); } } @@ -279,12 +278,12 @@ fn resolveUnwrapOptionalType(analysis_ctx: *AnalysisContext, opt: *ast.Node) ?*a } /// Resolves the child type of a defer type -fn resolveDerefType(analysis_ctx: *AnalysisContext, deref: *ast.Node) ?*ast.Node { - if (deref.cast(ast.Node.PrefixOp)) |pop| { +fn resolveDerefType(store: *DocumentStore, deref: NodeWithHandle) !?NodeWithHandle { + if (deref.node.cast(ast.Node.PrefixOp)) |pop| { if (pop.op == .PtrType) { - const op_token_id = analysis_ctx.tree().token_ids[pop.op_token]; + const op_token_id = deref.handle.tree.token_ids[pop.op_token]; switch (op_token_id) { - .Asterisk => return resolveTypeOfNode(analysis_ctx, pop.rhs), + .Asterisk => return try resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = deref.handle }), .LBracket, .AsteriskAsterisk => return null, else => unreachable, } @@ -293,9 +292,9 @@ fn resolveDerefType(analysis_ctx: *AnalysisContext, deref: *ast.Node) ?*ast.Node return null; } -fn makeSliceType(analysis_ctx: *AnalysisContext, child_type: *ast.Node) ?*ast.Node { +fn makeSliceType(arena: *std.heap.ArenaAllocator, child_type: *ast.Node) ?*ast.Node { // TODO: Better values for fields, better way to do this? - var slice_type = analysis_ctx.arena.allocator.create(ast.Node.PrefixOp) catch return null; + var slice_type = arena.allocator.create(ast.Node.PrefixOp) catch return null; slice_type.* = .{ .op_token = child_type.firstToken(), .op = .{ @@ -315,26 +314,27 @@ fn makeSliceType(analysis_ctx: *AnalysisContext, child_type: *ast.Node) ?*ast.No /// Resolves bracket access type (both slicing and array access) fn resolveBracketAccessType( - analysis_ctx: *AnalysisContext, - lhs: *ast.Node, + store: *DocumentStore, + lhs: NodeWithHandle, + arena: *std.heap.ArenaAllocator, rhs: enum { Single, Range }, -) ?*ast.Node { - if (lhs.cast(ast.Node.PrefixOp)) |pop| { +) !?NodeWithHandle { + if (lhs.node.cast(ast.Node.PrefixOp)) |pop| { switch (pop.op) { .SliceType => { - if (rhs == .Single) return resolveTypeOfNode(analysis_ctx, pop.rhs); + if (rhs == .Single) return resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = lhs.handle }); return lhs; }, .ArrayType => { - if (rhs == .Single) return resolveTypeOfNode(analysis_ctx, pop.rhs); - return makeSliceType(analysis_ctx, pop.rhs); + if (rhs == .Single) return resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = lhs.handle }); + return NodeWithHandle{ .node = makeSliceType(arena, pop.rhs), .handle = lhs.handle }; }, .PtrType => { if (pop.rhs.cast(std.zig.ast.Node.PrefixOp)) |child_pop| { switch (child_pop.op) { .ArrayType => { if (rhs == .Single) { - return resolveTypeOfNode(analysis_ctx, child_pop.rhs); + return resolveTypeOfNode(store, .{ .node = child_pop.rhs, .handle = lhs.handle }); } return lhs; }, @@ -349,12 +349,13 @@ fn resolveBracketAccessType( } /// Called to remove one level of pointerness before a field access -fn resolveFieldAccessLhsType(analysis_ctx: *AnalysisContext, lhs: *ast.Node) *ast.Node { - return resolveDerefType(analysis_ctx, lhs) orelse lhs; +fn resolveFieldAccessLhsType(store: *DocumentStore, lhs: NodeWithHandle) !NodeWithHandle { + return resolveDerefType(store, lhs) orelse lhs; } +// @TODO try errors /// Resolves the type of a node -pub fn resolveTypeOfNode(analysis_ctx: *AnalysisContext, node: *ast.Node) ?*ast.Node { +pub fn resolveTypeOfNode(store: *DocumentStore, node_handle: NodeWithHandle) !?NodeWithHandle { switch (node.id) { .VarDecl => { const vari = node.cast(ast.Node.VarDecl).?; @@ -609,52 +610,50 @@ pub fn collectImports(import_arr: *std.ArrayList([]const u8), tree: *ast.Tree) ! } } -fn checkForContainerAndResolveFieldAccessLhsType(analysis_ctx: *AnalysisContext, node: *ast.Node) *ast.Node { - const current_node = resolveFieldAccessLhsType(analysis_ctx, node); - - if (current_node.id == .ContainerDecl or current_node.id == .Root) { - // TODO: Handle errors - analysis_ctx.onContainer(current_node) catch {}; - } - - return current_node; -} +pub const NodeWithHandle = struct { + node: *ast.Node, + handle: *DocumentStore.Handle, +}; pub fn getFieldAccessTypeNode( - analysis_ctx: *AnalysisContext, + store: *DocumentStore, + arena: *std.heap.ArenaAllocator, + handle: *DocumentStore.Handle, tokenizer: *std.zig.Tokenizer, -) ?*ast.Node { - var current_node = analysis_ctx.in_container; +) !?NodeWithHandle { + var current_node = NodeWithHandle{ + .node = &handle.tree.root_node.base, + .handle = handle, + }; while (true) { const tok = tokenizer.next(); switch (tok.id) { - .Eof => return resolveFieldAccessLhsType(analysis_ctx, current_node), + .Eof => return try resolveFieldAccessLhsType(store, current_node), .Identifier => { - if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, tokenizer.buffer[tok.loc.start..tok.loc.end])) |child| { - if (resolveTypeOfNode(analysis_ctx, child)) |child_type| { - current_node = child_type; - } else return null; + if (try lookupSymbolGlobal(store, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], tok.loc.start)) |child| { + current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, .Period => { const after_period = tokenizer.next(); switch (after_period.id) { - .Eof => return resolveFieldAccessLhsType(analysis_ctx, current_node), + .Eof => return try resolveFieldAccessLhsType(store, current_node), .Identifier => { - if (after_period.loc.end == tokenizer.buffer.len) return resolveFieldAccessLhsType(analysis_ctx, current_node); + if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, current_node); - current_node = checkForContainerAndResolveFieldAccessLhsType(analysis_ctx, current_node); - 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; + current_node = resolveFieldAccessLhsType(store, current_node); + if (current_node.node.id != .ContainerDecl and current_node.node.id != .Root) { + // @TODO Is this ok? + return null; + } + + if (lookupSymbolContainer(store, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { + current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, .QuestionMark => { - if (resolveUnwrapOptionalType(analysis_ctx, current_node)) |child_type| { - current_node = child_type; - } else return null; + current_node = (try resolveUnwrapOptionalType(store, current_node)) orelse return null; }, else => { std.debug.warn("Unrecognized token {} after period.\n", .{after_period.id}); @@ -663,15 +662,13 @@ pub fn getFieldAccessTypeNode( } }, .PeriodAsterisk => { - if (resolveDerefType(analysis_ctx, current_node)) |child_type| { - current_node = child_type; - } else return null; + current_node = (try resolveDerefType(store, current_node)) orelse return null; }, .LParen => { - switch (current_node.id) { + switch (current_node.node.id) { .FnProto => { - const func = current_node.cast(ast.Node.FnProto).?; - if (resolveReturnType(analysis_ctx, func)) |ret| { + const func = current_node.node.cast(ast.Node.FnProto).?; + if (try resolveReturnType(store, func, current_node.handle)) |ret| { current_node = ret; // Skip to the right paren var paren_count: usize = 1; @@ -704,26 +701,16 @@ pub fn getFieldAccessTypeNode( } } else return null; - if (resolveBracketAccessType( - analysis_ctx, - current_node, - if (is_range) .Range else .Single, - )) |child_type| { - current_node = child_type; - } else return null; + current_node = (try resolveBracketAccessType(store, current_node, arena, if (is_range) .Range else .Single)) orelse return null; }, else => { std.debug.warn("Unimplemented token: {}\n", .{tok.id}); return null; }, } - - if (current_node.id == .ContainerDecl or current_node.id == .Root) { - analysis_ctx.onContainer(current_node) catch return null; - } } - return resolveFieldAccessLhsType(analysis_ctx, current_node); + return try resolveFieldAccessLhsType(store, current_node); } pub fn isNodePublic(tree: *ast.Tree, node: *ast.Node) bool { @@ -1069,8 +1056,6 @@ pub fn getDocumentSymbols(allocator: *std.mem.Allocator, tree: *ast.Tree) ![]typ return symbols.items; } -const DocumentStore = @import("document_store.zig"); - pub const Declaration = union(enum) { ast_node: *ast.Node, param_decl: *ast.Node.FnProto.ParamDecl, @@ -1105,6 +1090,35 @@ pub const DeclWithHandle = struct { .switch_payload => |sp| tree.tokenLocation(0, sp.node.value_symbol.firstToken()), }; } + + fn resolveType(self: DeclWithHandle, store: *DocumentStore, arena: *std.heap.ArenaAllocator) !?NodeWithHandle { + // resolveTypeOfNode(store: *DocumentStore, node_handle: NodeWithHandle) + return switch (self.decl) { + .ast_node => |node| try resolveTypeOfNode(store, .{ .node = node, .handle = self.handle }), + .param_decl => |param_decl| switch (param_decl.param_type) { + .type_expr => |type_node| try resolveTypeOfNode(store, .{ .node = node, .handle = self.handle }), + else => null, + }, + .pointer_payload => |pay| try resolveUnwrapOptionalType( + store, + try resolveTypeOfNode(store, .{ + .node = pay.condition, + .handle = self.handle, + }) orelse return null, + ), + .array_payload => |pay| try resolveBracketAccessType( + store, + .{ + .node = pay.array_expr, + .handle = self.handle, + }, + arena, + .Single, + ), + // TODO Resolve switch payload types + .switch_payload => |pay| return null, + }; + } }; pub fn iterateSymbolsGlobal( @@ -1157,7 +1171,9 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, return null; } -pub fn lookupSymbolContainer(store: *DocumentScope, handle: *DocumentStore.Handle, container: *ast.Node, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { +pub fn lookupSymbolContainer(store: *DocumentScope, container_handle: NodeWithHandle, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { + const container = container_handle.node; + const handle = container_handle.handle; std.debug.assert(container.id == .ContainerDecl or container.id == .Root); // Find the container scope. var maybe_container_scope: ?*Scope = null; diff --git a/src/document_store.zig b/src/document_store.zig index 4fe1429..a9012ac 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -78,6 +78,7 @@ handles: std.StringHashMap(*Handle), zig_exe_path: ?[]const u8, build_files: std.ArrayListUnmanaged(*BuildFile), build_runner_path: []const u8, +std_uri: ?[]const u8, error_completions: TagStore, enum_completions: TagStore, @@ -87,12 +88,14 @@ pub fn init( allocator: *std.mem.Allocator, zig_exe_path: ?[]const u8, build_runner_path: []const u8, + zig_lib_path: ?[]const u8, ) !void { self.allocator = allocator; self.handles = std.StringHashMap(*Handle).init(allocator); self.zig_exe_path = zig_exe_path; self.build_files = .{}; self.build_runner_path = build_runner_path; + self.std_uri = try stdUriFromLibPath(allocator, zig_lib_path); self.error_completions = TagStore.init(allocator); self.enum_completions = TagStore.init(allocator); } @@ -393,7 +396,7 @@ fn refreshDocument(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const const std_uri = try stdUriFromLibPath(&arena.allocator, zig_lib_path); for (import_strs.items) |str| { - const uri = (try uriFromImportStr(self, &arena.allocator, handle.*, str, std_uri)) orelse continue; + const uri = (try self.uriFromImportStr(&arena.allocator, handle.*, str)) orelse continue; var idx: usize = 0; exists_loop: while (idx < still_exist.len) : (idx += 1) { @@ -496,14 +499,13 @@ pub fn applyChanges( } pub fn uriFromImportStr( - store: *DocumentStore, + self: *DocumentStore, allocator: *std.mem.Allocator, handle: Handle, import_str: []const u8, - std_uri: ?[]const u8, ) !?[]const u8 { if (std.mem.eql(u8, import_str, "std")) { - if (std_uri) |uri| return try std.mem.dupe(allocator, u8, uri) else { + if (self.std_uri) |uri| return try std.mem.dupe(allocator, u8, uri) else { std.debug.warn("Cannot resolve std library import, path is null.\n", .{}); return null; } @@ -534,93 +536,72 @@ pub fn uriFromImportStr( } } -pub const AnalysisContext = struct { - store: *DocumentStore, - handle: *Handle, - // This arena is used for temporary allocations while analyzing, - // not for the tree allocations. - arena: *std.heap.ArenaAllocator, - std_uri: ?[]const u8, - error_completions: *TagStore, - enum_completions: *TagStore, +pub fn resolveImport(self: *DocumentStore, handle: *Handle, import_str: []const u8) !?*Handle { + const allocator = self.allocator; + const final_uri = (try self.uriFromImportStr( + self.allocator, + handle.*, + import_str, + )) orelse return null; - pub fn tree(self: AnalysisContext) *std.zig.ast.Tree { - return self.handle.tree; + std.debug.warn("Import final URI: {}\n", .{final_uri}); + var consumed_final_uri = false; + defer if (!consumed_final_uri) allocator.free(final_uri); + + // Check if we already imported this. + for (handle.import_uris.items) |uri| { + // If we did, set our new handle and return the parsed tree root node. + if (std.mem.eql(u8, uri, final_uri)) { + return self.getHandle(final_uri); + } } - pub fn onImport(self: *AnalysisContext, import_str: []const u8) !?*std.zig.ast.Node { - const allocator = self.store.allocator; - const final_uri = (try uriFromImportStr( - self.store, - self.store.allocator, - self.handle.*, - import_str, - self.std_uri, - )) orelse return null; + // New import. + // Check if the import is already opened by others. + if (self.getHandle(final_uri)) |new_handle| { + // If it is, append it to our imports, increment the count, set our new handle + // and return the parsed tree root node. + try handle.import_uris.append(final_uri); + consumed_final_uri = true; - std.debug.warn("Import final URI: {}\n", .{final_uri}); - var consumed_final_uri = false; - defer if (!consumed_final_uri) allocator.free(final_uri); + new_handle.count += 1; + return new_handle; + } - // Check if we already imported this. - for (self.handle.import_uris.items) |uri| { - // 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; - return &self.tree().root_node.base; - } - } + // New document, read the file then call into openDocument. + const file_path = try URI.parse(allocator, final_uri); + defer allocator.free(file_path); - // New import. - // Check if the import is already opened by others. - if (self.store.getHandle(final_uri)) |new_handle| { - // If it is, append it to our imports, increment the count, set our new handle - // and return the parsed tree root node. - try self.handle.import_uris.append(final_uri); - consumed_final_uri = true; + var file = std.fs.cwd().openFile(file_path, .{}) catch { + std.debug.warn("Cannot open import file {}\n", .{file_path}); + return null; + }; - new_handle.count += 1; - self.handle = new_handle; - return &self.tree().root_node.base; - } + defer file.close(); + const size = std.math.cast(usize, try file.getEndPos()) catch std.math.maxInt(usize); - // New document, read the file then call into openDocument. - const file_path = try URI.parse(allocator, final_uri); - defer allocator.free(file_path); + { + const file_contents = try allocator.alloc(u8, size); + errdefer allocator.free(file_contents); - var file = std.fs.cwd().openFile(file_path, .{}) catch { - std.debug.warn("Cannot open import file {}\n", .{file_path}); + file.inStream().readNoEof(file_contents) catch { + std.debug.warn("Could not read from file {}\n", .{file_path}); return null; }; - defer file.close(); - const size = std.math.cast(usize, try file.getEndPos()) catch std.math.maxInt(usize); + // Add to import table of current handle. + try handle.import_uris.append(final_uri); + consumed_final_uri = true; - { - const file_contents = try allocator.alloc(u8, size); - errdefer allocator.free(file_contents); - - file.inStream().readNoEof(file_contents) catch { - std.debug.warn("Could not read from file {}\n", .{file_path}); - return null; - }; - - // Add to import table of current handle. - try self.handle.import_uris.append(final_uri); - consumed_final_uri = true; - - // 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); - } - - return &self.tree().root_node.base; + // 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); + return try self.newDocument(duped_final_uri, file_contents); } -}; +} -pub fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 { +fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 { if (zig_lib_path) |zpath| { const std_path = std.fs.path.resolve(allocator, &[_][]const u8{ zpath, "./std/std.zig", @@ -637,23 +618,6 @@ pub fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u return null; } -pub fn analysisContext( - self: *DocumentStore, - handle: *Handle, - arena: *std.heap.ArenaAllocator, - zig_lib_path: ?[]const u8, -) !AnalysisContext { - const std_uri = try stdUriFromLibPath(&arena.allocator, zig_lib_path); - return AnalysisContext{ - .store = self, - .handle = handle, - .arena = arena, - .std_uri = std_uri, - .error_completions = &self.error_completions, - .enum_completions = &self.enum_completions, - }; -} - pub fn deinit(self: *DocumentStore) void { var entry_iterator = self.handles.iterator(); while (entry_iterator.next()) |entry| { @@ -681,6 +645,10 @@ pub fn deinit(self: *DocumentStore) void { self.allocator.destroy(build_file); } + if (self.std_uri) |std_uri| { + self.allocator.free(std_uri); + } + self.build_files.deinit(self.allocator); self.error_completions.deinit(); self.enum_completions.deinit(); diff --git a/src/main.zig b/src/main.zig index 095c820..fbd7b89 100644 --- a/src/main.zig +++ b/src/main.zig @@ -247,9 +247,7 @@ fn resolveVarDeclFnAlias(analysis_ctx: *DocumentStore.AnalysisContext, decl: *st fn nodeToCompletion( list: *std.ArrayList(types.CompletionItem), - analysis_ctx: *DocumentStore.AnalysisContext, - orig_handle: *DocumentStore.Handle, - node: *std.zig.ast.Node, + node_handle: analysis.NodeWithHandle, config: Config, ) error{OutOfMemory}!void { const doc = if (try analysis.getDocComments(list.allocator, analysis_ctx.tree(), node)) |doc_comments| @@ -519,7 +517,6 @@ fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *Document &arena.allocator, handle.*, import_str, - try DocumentStore.stdUriFromLibPath(&arena.allocator, config.zig_lib_path), )) orelse return try respondGeneric(id, null_result_response); try send(types.Response{ @@ -545,9 +542,7 @@ const DeclToCompletionContext = struct { fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { switch (decl_handle.decl.*) { .ast_node => |node| { - // @TODO Remove analysis context - var analysis_ctx = try document_store.analysisContext(decl_handle.handle, context.arena, context.config.zig_lib_path); - try nodeToCompletion(context.completions, &analysis_ctx, decl_handle.handle, node, context.config.*); + try nodeToCompletion(context.completions, .{ .node = node, .handle = decl_handle.handle }, context.config.*); }, else => {}, // @TODO The rest @@ -568,16 +563,6 @@ fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore. }; try analysis.iterateSymbolsGlobal(&document_store, handle, pos_index, decltoCompletion, context); - // for (analysis_ctx.scope_nodes) |decl_ptr| { - // var decl = decl_ptr.*; - // if (decl.id == .Use) { - // std.debug.warn("Found use!", .{}); - // continue; - // } - - // try nodeToCompletion(&completions, &analysis_ctx, handle, decl_ptr, config); - // } - try send(types.Response{ .id = id, .result = .{ @@ -1115,13 +1100,13 @@ pub fn main() anyerror!void { } if (config.build_runner_path) |build_runner_path| { - try document_store.init(allocator, zig_exe_path, try std.mem.dupe(allocator, u8, build_runner_path)); + try document_store.init(allocator, zig_exe_path, try std.mem.dupe(allocator, u8, build_runner_path), config.zig_lib_path); } else { var exe_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; const exe_dir_path = try std.fs.selfExeDirPath(&exe_dir_bytes); const build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ exe_dir_path, "build_runner.zig" }); - try document_store.init(allocator, zig_exe_path, build_runner_path); + try document_store.init(allocator, zig_exe_path, build_runner_path, config.zig_lib_path); } defer document_store.deinit(); From 3bdb2ee4445a0362a15cdffcb232dc2ea46fb9e0 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 20:48:40 +0300 Subject: [PATCH 04/14] More progress --- src/analysis.zig | 108 ++++++++++++++++-------------- src/main.zig | 170 ++++++++++++++++++++++++----------------------- 2 files changed, 145 insertions(+), 133 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index a8f31ab..339536d 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -244,13 +244,13 @@ fn findReturnStatement(tree: *ast.Tree, fn_decl: *ast.Node.FnProto) ?*ast.Node.C } /// Resolves the return type of a function -fn resolveReturnType(store: *DocumentStore, fn_decl: *ast.Node.FnProto, handle: *DocumentStore.Handle) !?NodeWithHandle { +fn resolveReturnType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, fn_decl: *ast.Node.FnProto, handle: *DocumentStore.Handle) !?NodeWithHandle { if (isTypeFunction(handle.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(handle.tree, fn_decl) orelse return null; if (ret.rhs) |rhs| - if (try resolveTypeOfNode(store, .{ .node = rhs, .handle = handle })) |res_rhs| switch (res_rhs.node.id) { + if (try resolveTypeOfNode(store, arena, .{ .node = rhs, .handle = handle })) |res_rhs| switch (res_rhs.node.id) { .ContainerDecl => { return res_rhs; }, @@ -261,16 +261,16 @@ fn resolveReturnType(store: *DocumentStore, fn_decl: *ast.Node.FnProto, handle: } return switch (fn_decl.return_type) { - .Explicit, .InferErrorSet => |return_type| try resolveTypeOfNode(store, .{ .node = return_type, .handle = handle }), + .Explicit, .InferErrorSet => |return_type| try resolveTypeOfNode(store, arena, .{ .node = return_type, .handle = handle }), .Invalid => null, }; } /// Resolves the child type of an optional type -fn resolveUnwrapOptionalType(store: *DocumentStore, opt: NodeWithHandle) !?NodeWithHandle { +fn resolveUnwrapOptionalType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, opt: NodeWithHandle) !?NodeWithHandle { if (opt.node.cast(ast.Node.PrefixOp)) |prefix_op| { if (prefix_op.op == .OptionalType) { - return try resolveTypeOfNode(store, .{ .node = prefix_op.rhs, .handle = opt.handle }); + return try resolveTypeOfNode(store, arena, .{ .node = prefix_op.rhs, .handle = opt.handle }); } } @@ -278,12 +278,12 @@ fn resolveUnwrapOptionalType(store: *DocumentStore, opt: NodeWithHandle) !?NodeW } /// Resolves the child type of a defer type -fn resolveDerefType(store: *DocumentStore, deref: NodeWithHandle) !?NodeWithHandle { +fn resolveDerefType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, deref: NodeWithHandle) !?NodeWithHandle { if (deref.node.cast(ast.Node.PrefixOp)) |pop| { if (pop.op == .PtrType) { const op_token_id = deref.handle.tree.token_ids[pop.op_token]; switch (op_token_id) { - .Asterisk => return try resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = deref.handle }), + .Asterisk => return try resolveTypeOfNode(store, arena, .{ .node = pop.rhs, .handle = deref.handle }), .LBracket, .AsteriskAsterisk => return null, else => unreachable, } @@ -315,26 +315,26 @@ fn makeSliceType(arena: *std.heap.ArenaAllocator, child_type: *ast.Node) ?*ast.N /// Resolves bracket access type (both slicing and array access) fn resolveBracketAccessType( store: *DocumentStore, - lhs: NodeWithHandle, arena: *std.heap.ArenaAllocator, + lhs: NodeWithHandle, rhs: enum { Single, Range }, ) !?NodeWithHandle { if (lhs.node.cast(ast.Node.PrefixOp)) |pop| { switch (pop.op) { .SliceType => { - if (rhs == .Single) return resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = lhs.handle }); + if (rhs == .Single) return try resolveTypeOfNode(store, arena, .{ .node = pop.rhs, .handle = lhs.handle }); return lhs; }, .ArrayType => { - if (rhs == .Single) return resolveTypeOfNode(store, .{ .node = pop.rhs, .handle = lhs.handle }); - return NodeWithHandle{ .node = makeSliceType(arena, pop.rhs), .handle = lhs.handle }; + if (rhs == .Single) return try resolveTypeOfNode(store, arena, .{ .node = pop.rhs, .handle = lhs.handle }); + return NodeWithHandle{ .node = makeSliceType(arena, pop.rhs) orelse return null, .handle = lhs.handle }; }, .PtrType => { if (pop.rhs.cast(std.zig.ast.Node.PrefixOp)) |child_pop| { switch (child_pop.op) { .ArrayType => { if (rhs == .Single) { - return resolveTypeOfNode(store, .{ .node = child_pop.rhs, .handle = lhs.handle }); + return try resolveTypeOfNode(store, arena, .{ .node = child_pop.rhs, .handle = lhs.handle }); } return lhs; }, @@ -349,39 +349,36 @@ fn resolveBracketAccessType( } /// Called to remove one level of pointerness before a field access -fn resolveFieldAccessLhsType(store: *DocumentStore, lhs: NodeWithHandle) !NodeWithHandle { - return resolveDerefType(store, lhs) orelse lhs; +fn resolveFieldAccessLhsType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, lhs: NodeWithHandle) !NodeWithHandle { + return (try resolveDerefType(store, arena, lhs)) orelse lhs; } -// @TODO try errors /// Resolves the type of a node -pub fn resolveTypeOfNode(store: *DocumentStore, node_handle: NodeWithHandle) !?NodeWithHandle { +pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, node_handle: NodeWithHandle) error{OutOfMemory}!?NodeWithHandle { + const node = node_handle.node; + const handle = node_handle.handle; + switch (node.id) { .VarDecl => { const vari = node.cast(ast.Node.VarDecl).?; - - return resolveTypeOfNode(analysis_ctx, vari.type_node orelse vari.init_node.?) orelse null; + return try resolveTypeOfNode(store, arena, .{ .node = vari.type_node orelse vari.init_node.?, .handle = handle }); }, .Identifier => { - if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, analysis_ctx.tree().getNodeSource(node))) |child| { - if (child == node) return null; - return resolveTypeOfNode(analysis_ctx, child); - } else return null; + if (try lookupSymbolGlobal(store, handle, handle.tree.getNodeSource(node), handle.tree.token_locs[node.firstToken()].start)) |child| { + return try child.resolveType(store, arena); + } + return null; }, .ContainerField => { const field = node.cast(ast.Node.ContainerField).?; - return resolveTypeOfNode(analysis_ctx, field.type_expr orelse return null); + return try resolveTypeOfNode(store, arena, .{ .node = field.type_expr orelse return null, .handle = handle }); }, .Call => { const call = node.cast(ast.Node.Call).?; - const previous_tree = analysis_ctx.tree(); - - const decl = resolveTypeOfNode(analysis_ctx, call.lhs) orelse return null; - if (decl.cast(ast.Node.FnProto)) |fn_decl| { - if (previous_tree != analysis_ctx.tree()) { - return resolveReturnType(analysis_ctx, fn_decl); - } + // @TODO use BoundTypeParams: ParamDecl -> NodeWithHandle or something + const decl = (try resolveTypeOfNode(store, arena, .{ .node = call.lhs, .handle = handle })) orelse return null; + if (decl.node.cast(ast.Node.FnProto)) |fn_decl| { // Add type param values to the scope nodes const param_len = std.math.min(call.params_len, fn_decl.params_len); var scope_nodes = std.ArrayList(*ast.Node).fromOwnedSlice(&analysis_ctx.arena.allocator, analysis_ctx.scope_nodes); @@ -629,7 +626,7 @@ pub fn getFieldAccessTypeNode( while (true) { const tok = tokenizer.next(); switch (tok.id) { - .Eof => return try resolveFieldAccessLhsType(store, current_node), + .Eof => return try resolveFieldAccessLhsType(store, arena, current_node), .Identifier => { if (try lookupSymbolGlobal(store, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], tok.loc.start)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; @@ -638,22 +635,22 @@ pub fn getFieldAccessTypeNode( .Period => { const after_period = tokenizer.next(); switch (after_period.id) { - .Eof => return try resolveFieldAccessLhsType(store, current_node), + .Eof => return try resolveFieldAccessLhsType(store, arena, current_node), .Identifier => { - if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, current_node); + if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, arena, current_node); - current_node = resolveFieldAccessLhsType(store, current_node); + current_node = try resolveFieldAccessLhsType(store, arena, current_node); if (current_node.node.id != .ContainerDecl and current_node.node.id != .Root) { // @TODO Is this ok? return null; } - if (lookupSymbolContainer(store, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { + if (try lookupSymbolContainer(store, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, .QuestionMark => { - current_node = (try resolveUnwrapOptionalType(store, current_node)) orelse return null; + current_node = (try resolveUnwrapOptionalType(store, arena, current_node)) orelse return null; }, else => { std.debug.warn("Unrecognized token {} after period.\n", .{after_period.id}); @@ -662,13 +659,13 @@ pub fn getFieldAccessTypeNode( } }, .PeriodAsterisk => { - current_node = (try resolveDerefType(store, current_node)) orelse return null; + current_node = (try resolveDerefType(store, arena, current_node)) orelse return null; }, .LParen => { switch (current_node.node.id) { .FnProto => { const func = current_node.node.cast(ast.Node.FnProto).?; - if (try resolveReturnType(store, func, current_node.handle)) |ret| { + if (try resolveReturnType(store, arena, func, current_node.handle)) |ret| { current_node = ret; // Skip to the right paren var paren_count: usize = 1; @@ -701,7 +698,7 @@ pub fn getFieldAccessTypeNode( } } else return null; - current_node = (try resolveBracketAccessType(store, current_node, arena, if (is_range) .Range else .Single)) orelse return null; + current_node = (try resolveBracketAccessType(store, arena, current_node, if (is_range) .Range else .Single)) orelse return null; }, else => { std.debug.warn("Unimplemented token: {}\n", .{tok.id}); @@ -710,7 +707,7 @@ pub fn getFieldAccessTypeNode( } } - return try resolveFieldAccessLhsType(store, current_node); + return try resolveFieldAccessLhsType(store, arena, current_node); } pub fn isNodePublic(tree: *ast.Tree, node: *ast.Node) bool { @@ -1092,27 +1089,27 @@ pub const DeclWithHandle = struct { } fn resolveType(self: DeclWithHandle, store: *DocumentStore, arena: *std.heap.ArenaAllocator) !?NodeWithHandle { - // resolveTypeOfNode(store: *DocumentStore, node_handle: NodeWithHandle) - return switch (self.decl) { - .ast_node => |node| try resolveTypeOfNode(store, .{ .node = node, .handle = self.handle }), + return switch (self.decl.*) { + .ast_node => |node| try resolveTypeOfNode(store, arena, .{ .node = node, .handle = self.handle }), .param_decl => |param_decl| switch (param_decl.param_type) { - .type_expr => |type_node| try resolveTypeOfNode(store, .{ .node = node, .handle = self.handle }), + .type_expr => |type_node| try resolveTypeOfNode(store, arena, .{ .node = type_node, .handle = self.handle }), else => null, }, .pointer_payload => |pay| try resolveUnwrapOptionalType( store, - try resolveTypeOfNode(store, .{ + arena, + (try resolveTypeOfNode(store, arena, .{ .node = pay.condition, .handle = self.handle, - }) orelse return null, + })) orelse return null, ), .array_payload => |pay| try resolveBracketAccessType( store, + arena, .{ .node = pay.array_expr, .handle = self.handle, }, - arena, .Single, ), // TODO Resolve switch payload types @@ -1171,18 +1168,19 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, return null; } -pub fn lookupSymbolContainer(store: *DocumentScope, container_handle: NodeWithHandle, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { +pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHandle, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { const container = container_handle.node; const handle = container_handle.handle; std.debug.assert(container.id == .ContainerDecl or container.id == .Root); // Find the container scope. var maybe_container_scope: ?*Scope = null; for (handle.document_scope.scopes) |*scope| { - switch (scope.*) { + switch (scope.*.data) { .container => |node| if (node == container) { maybe_container_scope = scope; break; }, + else => {}, } } @@ -1194,7 +1192,7 @@ pub fn lookupSymbolContainer(store: *DocumentScope, container_handle: NodeWithHa }, else => {}, } - return &candidate.value; + return DeclWithHandle{ .decl = &candidate.value, .handle = handle }; } for (container_scope.uses) |use| { @@ -1206,7 +1204,7 @@ pub fn lookupSymbolContainer(store: *DocumentScope, container_handle: NodeWithHa } pub const DocumentScope = struct { - scopes: []const Scope, + scopes: []Scope, pub fn debugPrint(self: DocumentScope) void { for (self.scopes) |scope| { @@ -1338,6 +1336,7 @@ fn makeScopeInternal( .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .{ .function = node }, }; var scope_idx = scopes.items.len - 1; @@ -1365,6 +1364,7 @@ fn makeScopeInternal( .range = nodeSourceRange(tree, node), .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .{ .block = node }, }; var scope_idx = scopes.items.len - 1; @@ -1410,6 +1410,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -1437,6 +1438,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -1461,6 +1463,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -1488,6 +1491,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -1514,6 +1518,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); @@ -1554,6 +1559,7 @@ fn makeScopeInternal( }, .decls = std.StringHashMap(Declaration).init(allocator), .uses = &[0]*ast.Node.Use{}, + .tests = &[0]*ast.Node{}, .data = .other, }; errdefer scope.decls.deinit(); diff --git a/src/main.zig b/src/main.zig index fbd7b89..30afcfd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -81,7 +81,7 @@ fn respondGeneric(id: types.RequestId, response: []const u8) !void { const json_fmt = "{{\"jsonrpc\":\"2.0\",\"id\":"; const stdout_stream = stdout.outStream(); - try stdout_stream.print("Content-Length: {}\r\n\r\n" ++ json_fmt, .{ response.len + id_len + json_fmt.len - 1 }); + try stdout_stream.print("Content-Length: {}\r\n\r\n" ++ json_fmt, .{response.len + id_len + json_fmt.len - 1}); switch (id) { .Integer => |int| try stdout_stream.print("{}", .{int}), .String => |str| try stdout_stream.print("\"{}\"", .{str}), @@ -98,7 +98,7 @@ fn showMessage(@"type": types.MessageType, message: []const u8) !void { .params = .{ .ShowMessageParams = .{ .@"type" = @"type", - .message = message + .message = message, }, }, }); @@ -198,31 +198,32 @@ fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void { fn containerToCompletion( list: *std.ArrayList(types.CompletionItem), - analysis_ctx: *DocumentStore.AnalysisContext, + container_handle: analysis.NodeWithHandle, orig_handle: *DocumentStore.Handle, - container: *std.zig.ast.Node, config: Config, ) !void { + // @TODO Something like iterateSymbolsGlobal but for container to support uses. + + const container = container_handle.node; + const handle = container_handle.handle; + 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)) { - try nodeToCompletion(list, analysis_ctx, orig_handle, child_node, config); + if (orig_handle == handle or analysis.isNodePublic(handle.tree, child_node)) { + try nodeToCompletion(list, .{ .node = child_node, .handle = handle }, orig_handle, config); } } } -const ResolveVarDeclFnAliasRewsult = struct { - decl: *std.zig.ast.Node, - analysis_ctx: DocumentStore.AnalysisContext, -}; +fn resolveVarDeclFnAlias(arena: *std.heap.ArenaAllocator, decl_handle: analysis.NodeWithHandle) !analysis.NodeWithHandle { + const decl = decl_handle.node; + const handle = decl_handle.handle; -fn resolveVarDeclFnAlias(analysis_ctx: *DocumentStore.AnalysisContext, decl: *std.zig.ast.Node) !ResolveVarDeclFnAliasRewsult { if (decl.cast(std.zig.ast.Node.VarDecl)) |var_decl| { - var child_analysis_context = analysis_ctx.*; 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", handle.tree.tokenSlice(type_node.firstToken()))) { break :block var_decl.init_node orelse type_node; } break :block type_node; @@ -230,27 +231,28 @@ fn resolveVarDeclFnAlias(analysis_ctx: *DocumentStore.AnalysisContext, decl: *st break :block var_decl.init_node.?; }; - if (analysis.resolveTypeOfNode(&child_analysis_context, child_node)) |resolved_node| { - if (resolved_node.id == .FnProto) { - return ResolveVarDeclFnAliasRewsult{ - .decl = resolved_node, - .analysis_ctx = child_analysis_context, - }; + if (try analysis.resolveTypeOfNode(&document_store, arena, .{ .node = child_node, .handle = handle })) |resolved_node| { + // TODO Just return it anyway? + // This would allow deep goto definition etc. + // Try it out. + if (resolved_node.node.id == .FnProto) { + return resolved_node; } } } - return ResolveVarDeclFnAliasRewsult{ - .decl = decl, - .analysis_ctx = analysis_ctx.*, - }; + return decl_handle; } fn nodeToCompletion( list: *std.ArrayList(types.CompletionItem), node_handle: analysis.NodeWithHandle, + orig_handle: *DocumentStore.Handle, config: Config, ) error{OutOfMemory}!void { - const doc = if (try analysis.getDocComments(list.allocator, analysis_ctx.tree(), node)) |doc_comments| + const node = node_handle.node; + const handle = node_handle.handle; + + const doc = if (try analysis.getDocComments(list.allocator, handle.tree, node)) |doc_comments| types.MarkupContent{ .kind = .Markdown, .value = doc_comments, @@ -260,7 +262,7 @@ fn nodeToCompletion( switch (node.id) { .ErrorSetDecl, .Root, .ContainerDecl => { - try containerToCompletion(list, analysis_ctx, orig_handle, node, config); + try containerToCompletion(list, node_handle, orig_handle, config); }, .FnProto => { const func = node.cast(std.zig.ast.Node.FnProto).?; @@ -344,12 +346,12 @@ fn nodeToCompletion( .kind = .Field, }); }, - else => if (analysis.nodeToString(analysis_ctx.tree(), node)) |string| { + else => if (analysis.nodeToString(handle.tree, node)) |string| { try list.append(.{ .label = string, .kind = .Field, .documentation = doc, - .detail = analysis_ctx.tree().getNodeSource(node) + .detail = handle.tree.getNodeSource(node), }); }, } @@ -374,16 +376,17 @@ fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) []cons return text[start_idx + 1 .. end_idx]; } -fn gotoDefinitionSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.AnalysisContext, decl_handle: analysis.DeclWithHandle) !void { - var handle = analysis_ctx.handle; +fn gotoDefinitionSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle: analysis.DeclWithHandle) !void { + var handle = decl_handle.handle; const location = switch (decl_handle.decl.*) { .ast_node => |node| block: { - const result = try resolveVarDeclFnAlias(analysis_ctx, node); - handle = result.analysis_ctx.handle; - const name_token = analysis.getDeclNameToken(handle.tree, result.decl) orelse + const result = try resolveVarDeclFnAlias(arena, .{ .node = node, .handle = handle }); + handle = result.handle; + + const name_token = analysis.getDeclNameToken(result.handle.tree, result.node) orelse return try respondGeneric(id, null_result_response); - break :block handle.tree.tokenLocation(0, name_token); + break :block result.handle.tree.tokenLocation(0, name_token); }, else => decl_handle.location(), }; @@ -399,35 +402,41 @@ fn gotoDefinitionSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.Analys }); } -fn hoverSymbol(id: types.RequestId, analysis_ctx: *DocumentStore.AnalysisContext, decl: *std.zig.ast.Node) !void { - const result = try resolveVarDeclFnAlias(analysis_ctx, decl); +fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle: analysis.DeclWithHandle) !void { + switch (decl_handle.decl.*) { + .ast_node => |node| { + const result = try resolveVarDeclFnAlias(arena, .{ .node = node, .handle = decl_handle.handle }); - const doc_str = if (try analysis.getDocComments(&analysis_ctx.arena.allocator, result.analysis_ctx.tree(), result.decl)) |str| - str - else - ""; + const doc_str = if (try analysis.getDocComments(&arena.allocator, result.handle.tree, result.node)) |str| + str + else + ""; - const signature_str = switch (result.decl.id) { - .VarDecl => blk: { - const var_decl = result.decl.cast(std.zig.ast.Node.VarDecl).?; - break :blk analysis.getVariableSignature(result.analysis_ctx.tree(), var_decl); - }, - .FnProto => blk: { - const fn_decl = result.decl.cast(std.zig.ast.Node.FnProto).?; - break :blk analysis.getFunctionSignature(result.analysis_ctx.tree(), fn_decl); - }, - else => analysis.nodeToString(result.analysis_ctx.tree(), result.decl) orelse return try respondGeneric(id, null_result_response), - }; + const signature_str = switch (result.node.id) { + .VarDecl => blk: { + const var_decl = result.node.cast(std.zig.ast.Node.VarDecl).?; + break :blk analysis.getVariableSignature(result.handle.tree, var_decl); + }, + .FnProto => blk: { + const fn_decl = result.node.cast(std.zig.ast.Node.FnProto).?; + break :blk analysis.getFunctionSignature(result.handle.tree, fn_decl); + }, + else => analysis.nodeToString(result.handle.tree, result.node) orelse return try respondGeneric(id, null_result_response), + }; - const md_string = try std.fmt.allocPrint(&analysis_ctx.arena.allocator, "```zig\n{}\n```\n{}", .{ signature_str, doc_str }); - try send(types.Response{ - .id = id, - .result = .{ - .Hover = .{ - .contents = .{ .value = md_string }, - }, + const md_string = try std.fmt.allocPrint(&arena.allocator, "```zig\n{}\n```\n{}", .{ signature_str, doc_str }); + try send(types.Response{ + .id = id, + .result = .{ + .Hover = .{ + .contents = .{ .value = md_string }, + }, + }, + }); }, - }); + // @TODO Rest of decls + else => return try respondGeneric(id, null_result_response), + } } fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: *DocumentStore.Handle) !?analysis.DeclWithHandle { @@ -442,36 +451,33 @@ fn gotoDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *Document defer arena.deinit(); const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); - - var analysis_ctx = try document_store.analysisContext(decl.handle, &arena, config.zig_lib_path); - return try gotoDefinitionSymbol(id, &analysis_ctx, decl); + return try gotoDefinitionSymbol(id, &arena, decl); } fn hoverDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - const decl = (try getSymbolGlobal(&arena, pos_index, handle.*)) orelse return try respondGeneric(id, null_result_response); - var analysis_ctx = try document_store.analysisContext(handle, &arena, pos_index, config.zig_lib_path); - return try hoverSymbol(id, &analysis_ctx, decl); + const decl = (try getSymbolGlobal(&arena, pos_index, handle)) orelse return try respondGeneric(id, null_result_response); + return try hoverSymbol(id, &arena, decl); } fn getSymbolFieldAccess( - analysis_ctx: *DocumentStore.AnalysisContext, + handle: *DocumentStore.Handle, + arena: *std.heap.ArenaAllocator, position: types.Position, range: analysis.SourceRange, config: Config, -) !?*std.zig.ast.Node { - const pos_index = try analysis_ctx.handle.document.positionToIndex(position); - var name = identifierFromPosition(pos_index, analysis_ctx.handle.*); +) !?analysis.DeclWithHandle { + const pos_index = try handle.document.positionToIndex(position); + const name = identifierFromPosition(pos_index, handle.*); if (name.len == 0) return null; - const line = try analysis_ctx.handle.document.getLine(@intCast(usize, position.line)); + const line = try handle.document.getLine(@intCast(usize, position.line)); var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); - name = try std.mem.dupe(&analysis_ctx.arena.allocator, u8, name); - if (analysis.getFieldAccessTypeNode(analysis_ctx, &tokenizer)) |container| { - return analysis.getChild(analysis_ctx.tree(), container, name); + if (try analysis.getFieldAccessTypeNode(&document_store, arena, handle, &tokenizer)) |container_handle| { + return try analysis.lookupSymbolContainer(&document_store, container_handle, name, true); } return null; } @@ -486,9 +492,8 @@ fn gotoDefinitionFieldAccess( var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, config.zig_lib_path); - const decl = (try getSymbolFieldAccess(&analysis_ctx, position, range, config)) orelse return try respondGeneric(id, null_result_response); - return try gotoDefinitionSymbol(id, &analysis_ctx, decl); + const decl = (try getSymbolFieldAccess(handle, &arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); + return try gotoDefinitionSymbol(id, &arena, decl); } fn hoverDefinitionFieldAccess( @@ -501,9 +506,8 @@ fn hoverDefinitionFieldAccess( var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, try handle.document.positionToIndex(position), config.zig_lib_path); - const decl = (try getSymbolFieldAccess(&analysis_ctx, position, range, config)) orelse return try respondGeneric(id, null_result_response); - return try hoverSymbol(id, &analysis_ctx, decl); + const decl = (try getSymbolFieldAccess(handle, &arena, position, range, config)) orelse return try respondGeneric(id, null_result_response); + return try hoverSymbol(id, &arena, decl); } fn gotoDefinitionString(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { @@ -537,12 +541,13 @@ const DeclToCompletionContext = struct { completions: *std.ArrayList(types.CompletionItem), config: *const Config, arena: *std.heap.ArenaAllocator, + orig_handle: *DocumentStore.Handle, }; fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { switch (decl_handle.decl.*) { .ast_node => |node| { - try nodeToCompletion(context.completions, .{ .node = node, .handle = decl_handle.handle }, context.config.*); + try nodeToCompletion(context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, context.config.*); }, else => {}, // @TODO The rest @@ -560,6 +565,7 @@ fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore. .completions = &completions, .config = &config, .arena = &arena, + .orig_handle = handle, }; try analysis.iterateSymbolsGlobal(&document_store, handle, pos_index, decltoCompletion, context); @@ -578,15 +584,15 @@ fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, posit var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - var analysis_ctx = try document_store.analysisContext(handle, &arena, config.zig_lib_path); var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); const line = try handle.document.getLine(@intCast(usize, position.line)); var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); - if (analysis.getFieldAccessTypeNode(&analysis_ctx, &tokenizer)) |node| { - try nodeToCompletion(&completions, &analysis_ctx, handle, node, config); + if (try analysis.getFieldAccessTypeNode(&document_store, &arena, handle, &tokenizer)) |node| { + try nodeToCompletion(&completions, node, handle, config); } + try send(types.Response{ .id = id, .result = .{ From 05d75781dee4acf0678b767d6bfc1c4d53849f4d Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 21:24:17 +0300 Subject: [PATCH 05/14] Now builds! --- src/analysis.zig | 184 ++++++++++++++++++++++------------------------- src/main.zig | 59 +++++++-------- 2 files changed, 118 insertions(+), 125 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 339536d..e5264e6 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -376,110 +376,81 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, .Call => { const call = node.cast(ast.Node.Call).?; - // @TODO use BoundTypeParams: ParamDecl -> NodeWithHandle or something const decl = (try resolveTypeOfNode(store, arena, .{ .node = call.lhs, .handle = handle })) orelse return null; if (decl.node.cast(ast.Node.FnProto)) |fn_decl| { + // @TODO use BoundTypeParams: ParamDecl -> NodeWithHandle or something // Add type param values to the scope nodes - const param_len = std.math.min(call.params_len, fn_decl.params_len); - var scope_nodes = std.ArrayList(*ast.Node).fromOwnedSlice(&analysis_ctx.arena.allocator, analysis_ctx.scope_nodes); - var analysis_ctx_clone = analysis_ctx.clone() catch return null; - for (fn_decl.paramsConst()) |decl_param, param_idx| { - if (param_idx >= param_len) break; - if (decl_param.name_token == null) continue; - - const type_param = switch (decl_param.param_type) { - .type_expr => |type_node| if (type_node.cast(ast.Node.Identifier)) |ident| - std.mem.eql(u8, analysis_ctx.tree().tokenSlice(ident.token), "type") - else - false, - else => false, - }; - if (!type_param) continue; - - // TODO Handle errors better - // TODO This may invalidate the analysis context so we copy it. - // However, if the argument hits an import we just ignore it for now. - // Once we return our own types instead of directly using nodes we can fix this. - const call_param_type = resolveTypeOfNode(&analysis_ctx_clone, call.paramsConst()[param_idx]) orelse continue; - if (analysis_ctx_clone.handle != analysis_ctx.handle) { - analysis_ctx_clone = analysis_ctx.clone() catch return null; - continue; - } - - scope_nodes.append(makeVarDeclNode( - &analysis_ctx.arena.allocator, - decl_param.doc_comments, - decl_param.comptime_token, - decl_param.name_token.?, - null, - call_param_type, - ) catch return null) catch return null; - analysis_ctx.scope_nodes = scope_nodes.items; - } - - return resolveReturnType(analysis_ctx, fn_decl); + // const param_len = std.math.min(call.params_len, fn_decl.params_len); + // for (fn_decl.paramsConst()) |decl_param, param_idx| { + // if (param_idx >= param_len) break; + // if (decl_param.name_token == null) continue; + // const type_param = switch (decl_param.param_type) { + // .type_expr => |type_node| if (type_node.cast(ast.Node.Identifier)) |ident| + // std.mem.eql(u8, analysis_ctx.tree().tokenSlice(ident.token), "type") + // else + // false, + // else => false, + // }; + // if (!type_param) continue; + // const call_param_type = (try resolveTypeOfNode(store, arena, .{ .node = call.paramsConst()[param_idx], .handle = handle })) orelse continue; + // } + return try resolveReturnType(store, arena, fn_decl, decl.handle); } return decl; }, .StructInitializer => { const struct_init = node.cast(ast.Node.StructInitializer).?; - return resolveTypeOfNode(analysis_ctx, struct_init.lhs); + return try resolveTypeOfNode(store, arena, .{ .node = struct_init.lhs, .handle = handle }); }, .ErrorSetDecl => { const set = node.cast(ast.Node.ErrorSetDecl).?; var i: usize = 0; while (set.iterate(i)) |decl| : (i += 1) { - // TODO handle errors better? - analysis_ctx.error_completions.add(analysis_ctx.tree(), decl) catch {}; + try store.error_completions.add(handle.tree, decl); } - return node; + return node_handle; }, .SuffixOp => { const suffix_op = node.cast(ast.Node.SuffixOp).?; - switch (suffix_op.op) { - .UnwrapOptional => { - const left_type = resolveTypeOfNode(analysis_ctx, suffix_op.lhs) orelse return null; - return resolveUnwrapOptionalType(analysis_ctx, left_type); - }, - .Deref => { - const left_type = resolveTypeOfNode(analysis_ctx, suffix_op.lhs) orelse return null; - return resolveDerefType(analysis_ctx, left_type); - }, - .ArrayAccess => { - const left_type = resolveTypeOfNode(analysis_ctx, suffix_op.lhs) orelse return null; - return resolveBracketAccessType(analysis_ctx, left_type, .Single); - }, - .Slice => { - const left_type = resolveTypeOfNode(analysis_ctx, suffix_op.lhs) orelse return null; - return resolveBracketAccessType(analysis_ctx, left_type, .Range); - }, - else => {}, - } + const left_type = (try resolveTypeOfNode(store, arena, .{ .node = suffix_op.lhs, .handle = handle })) orelse return null; + return switch (suffix_op.op) { + .UnwrapOptional => try resolveUnwrapOptionalType(store, arena, left_type), + .Deref => try resolveDerefType(store, arena, left_type), + .ArrayAccess => try resolveBracketAccessType(store, arena, left_type, .Single), + .Slice => try resolveBracketAccessType(store, arena, left_type, .Range), + else => null, + }; }, .InfixOp => { const infix_op = node.cast(ast.Node.InfixOp).?; switch (infix_op.op) { .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; - // 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 rhs_str = nodeToString(handle.tree, infix_op.rhs) orelse return null; // If we are accessing a pointer type, remove one pointerness level :) - const left_type = resolveFieldAccessLhsType( - analysis_ctx, - resolveTypeOfNode(analysis_ctx, infix_op.lhs) orelse return null, + const left_type = try resolveFieldAccessLhsType( + store, + arena, + (try resolveTypeOfNode(store, arena, .{ + .node = infix_op.lhs, + .handle = handle, + })) orelse return null, ); - const child = getChild(analysis_ctx.tree(), left_type, rhs_str) orelse return null; - return resolveTypeOfNode(analysis_ctx, child); + // @TODO Error sets + if (left_type.node.id != .ContainerDecl and left_type.node.id != .Root) return null; + + if (try lookupSymbolContainer(store, left_type, rhs_str, true)) |child| { + return try child.resolveType(store, arena); + } else return null; }, .UnwrapOptional => { - const left_type = resolveTypeOfNode(analysis_ctx, infix_op.lhs) orelse return null; - return resolveUnwrapOptionalType(analysis_ctx, left_type); + const left_type = (try resolveTypeOfNode(store, arena, .{ + .node = infix_op.lhs, + .handle = handle, + })) orelse return null; + return try resolveUnwrapOptionalType(store, arena, left_type); }, - else => {}, + else => return null, } }, .PrefixOp => { @@ -489,13 +460,16 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, .ArrayType, .OptionalType, .PtrType, - => return node, + => return node_handle, .Try => { - const rhs_type = resolveTypeOfNode(analysis_ctx, prefix_op.rhs) orelse return null; - switch (rhs_type.id) { + const rhs_type = (try resolveTypeOfNode(store, arena, .{ .node = prefix_op.rhs, .handle = handle })) orelse return null; + switch (rhs_type.node.id) { .InfixOp => { - const infix_op = rhs_type.cast(ast.Node.InfixOp).?; - if (infix_op.op == .ErrorUnion) return infix_op.rhs; + const infix_op = rhs_type.node.cast(ast.Node.InfixOp).?; + if (infix_op.op == .ErrorUnion) return NodeWithHandle{ + .node = infix_op.rhs, + .handle = rhs_type.handle, + }; }, else => {}, } @@ -506,10 +480,10 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, }, .BuiltinCall => { const builtin_call = node.cast(ast.Node.BuiltinCall).?; - const call_name = analysis_ctx.tree().tokenSlice(builtin_call.builtin_token); + const call_name = handle.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; + return innermostContainer(handle, handle.tree.token_locs[builtin_call.firstToken()].start); } // TODO: https://github.com/ziglang/zig/issues/4335 @@ -528,7 +502,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, }); if (cast_map.has(call_name)) { if (builtin_call.params_len < 1) return null; - return resolveTypeOfNode(analysis_ctx, builtin_call.paramsConst()[0]); + return try resolveTypeOfNode(store, arena, .{ .node = builtin_call.paramsConst()[0], .handle = handle }); } if (!std.mem.eql(u8, call_name, "@import")) return null; @@ -537,31 +511,30 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, 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); - return analysis_ctx.onImport(import_str[1 .. import_str.len - 1]) catch |err| block: { + const import_str = handle.tree.tokenSlice(import_param.cast(ast.Node.StringLiteral).?.token); + const new_handle = (store.resolveImport(handle, import_str[1 .. import_str.len - 1]) catch |err| block: { std.debug.warn("Error {} while processing import {}\n", .{ err, import_str }); - break :block null; - }; + return null; + }) orelse return null; + + return NodeWithHandle{ .node = &new_handle.tree.root_node.base, .handle = new_handle }; }, .ContainerDecl => { - analysis_ctx.onContainer(node) catch return null; - const container = node.cast(ast.Node.ContainerDecl).?; - const kind = analysis_ctx.tree().token_ids[container.kind_token]; + const kind = handle.tree.token_ids[container.kind_token]; if (kind == .Keyword_struct or (kind == .Keyword_union and container.init_arg_expr == .None)) { - return node; + return node_handle; } var i: usize = 0; while (container.iterate(i)) |decl| : (i += 1) { if (decl.id != .ContainerField) continue; - // TODO handle errors better? - analysis_ctx.enum_completions.add(analysis_ctx.tree(), decl) catch {}; + try store.enum_completions.add(handle.tree, decl); } - return node; + return node_handle; }, - .MultilineStringLiteral, .StringLiteral, .FnProto => return node, + .MultilineStringLiteral, .StringLiteral, .FnProto => return node_handle, else => std.debug.warn("Type resolution case not implemented; {}\n", .{node.id}), } return null; @@ -640,6 +613,7 @@ pub fn getFieldAccessTypeNode( if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, arena, current_node); current_node = try resolveFieldAccessLhsType(store, arena, current_node); + // @TODO Error sets if (current_node.node.id != .ContainerDecl and current_node.node.id != .Root) { // @TODO Is this ok? return null; @@ -1141,6 +1115,22 @@ pub fn iterateSymbolsGlobal( } } +pub fn innermostContainer(handle: *DocumentStore.Handle, source_index: usize) NodeWithHandle { + var current = handle.document_scope.scopes[0].data.container; + if (handle.document_scope.scopes.len == 1) return .{ .node = current, .handle = handle }; + + for (handle.document_scope.scopes[1..]) |scope| { + if (source_index >= scope.range.start and source_index < scope.range.end) { + switch (scope.data) { + .container => |node| current = node, + else => {}, + } + } + if (scope.range.start > source_index) break; + } + return .{ .node = current, .handle = handle }; +} + pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, symbol: []const u8, source_index: usize) !?DeclWithHandle { for (handle.document_scope.scopes) |scope| { if (source_index >= scope.range.start and source_index < scope.range.end) { @@ -1162,7 +1152,7 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, } } - if (scope.range.start >= source_index) return null; + if (scope.range.start > source_index) return null; } return null; diff --git a/src/main.zig b/src/main.zig index 30afcfd..8a84917 100644 --- a/src/main.zig +++ b/src/main.zig @@ -197,6 +197,7 @@ fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void { } fn containerToCompletion( + arena: *std.heap.ArenaAllocator, list: *std.ArrayList(types.CompletionItem), container_handle: analysis.NodeWithHandle, orig_handle: *DocumentStore.Handle, @@ -211,7 +212,7 @@ fn containerToCompletion( while (container.iterate(child_idx)) |child_node| : (child_idx += 1) { // Declarations in the same file do not need to be public. if (orig_handle == handle or analysis.isNodePublic(handle.tree, child_node)) { - try nodeToCompletion(list, .{ .node = child_node, .handle = handle }, orig_handle, config); + try nodeToCompletion(arena, list, .{ .node = child_node, .handle = handle }, orig_handle, config); } } } @@ -244,6 +245,7 @@ fn resolveVarDeclFnAlias(arena: *std.heap.ArenaAllocator, decl_handle: analysis. } fn nodeToCompletion( + arena: *std.heap.ArenaAllocator, list: *std.ArrayList(types.CompletionItem), node_handle: analysis.NodeWithHandle, orig_handle: *DocumentStore.Handle, @@ -262,7 +264,7 @@ fn nodeToCompletion( switch (node.id) { .ErrorSetDecl, .Root, .ContainerDecl => { - try containerToCompletion(list, node_handle, orig_handle, config); + try containerToCompletion(arena, list, node_handle, orig_handle, config); }, .FnProto => { const func = node.cast(std.zig.ast.Node.FnProto).?; @@ -270,31 +272,32 @@ fn nodeToCompletion( const use_snippets = config.enable_snippets and client_capabilities.supports_snippets; const insert_text = if (use_snippets) blk: { - const skip_self_param = if (func.params_len > 0) param_check: { - var child_analysis_ctx = try analysis_ctx.clone(); - break :param_check switch (func.paramsConst()[0].param_type) { - .type_expr => |type_node| if (analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, type_node)) - true - else if (type_node.cast(std.zig.ast.Node.PrefixOp)) |prefix_op| - prefix_op.op == .PtrType and analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, prefix_op.rhs) - else - false, - else => false, - }; - } else - false; + // @TODO Rebuild this. + const skip_self_param = false; + // const skip_self_param = if (func.params_len > 0) param_check: { + // break :param_check switch (func.paramsConst()[0].param_type) { + // .type_expr => |type_node| if (analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, type_node)) + // true + // else if (type_node.cast(std.zig.ast.Node.PrefixOp)) |prefix_op| + // prefix_op.op == .PtrType and analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, prefix_op.rhs) + // else + // false, + // else => false, + // }; + // } else + // false; - break :blk try analysis.getFunctionSnippet(list.allocator, analysis_ctx.tree(), func, skip_self_param); + break :blk try analysis.getFunctionSnippet(&arena.allocator, handle.tree, func, skip_self_param); } else null; - const is_type_function = analysis.isTypeFunction(analysis_ctx.tree(), func); + const is_type_function = analysis.isTypeFunction(handle.tree, func); try list.append(.{ - .label = analysis_ctx.tree().tokenSlice(name_token), + .label = handle.tree.tokenSlice(name_token), .kind = if (is_type_function) .Struct else .Function, .documentation = doc, - .detail = analysis.getFunctionSignature(analysis_ctx.tree(), func), + .detail = analysis.getFunctionSignature(handle.tree, func), .insertText = insert_text, .insertTextFormat = if (use_snippets) .Snippet else .PlainText, }); @@ -302,18 +305,18 @@ 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 = handle.tree.token_ids[var_decl.mut_token] == .Keyword_const; - var result = try resolveVarDeclFnAlias(analysis_ctx, node); - if (result.decl != node) { - return try nodeToCompletion(list, &result.analysis_ctx, orig_handle, result.decl, config); + const result = try resolveVarDeclFnAlias(arena, node_handle); + if (result.node != node) { + return try nodeToCompletion(arena, list, result, orig_handle, config); } try list.append(.{ - .label = analysis_ctx.tree().tokenSlice(var_decl.name_token), + .label = handle.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(handle.tree, var_decl), }); }, .PrefixOp => { @@ -547,7 +550,7 @@ const DeclToCompletionContext = struct { fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { switch (decl_handle.decl.*) { .ast_node => |node| { - try nodeToCompletion(context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, context.config.*); + try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, context.config.*); }, else => {}, // @TODO The rest @@ -590,7 +593,7 @@ fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, posit var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); if (try analysis.getFieldAccessTypeNode(&document_store, &arena, handle, &tokenizer)) |node| { - try nodeToCompletion(&completions, node, handle, config); + try nodeToCompletion(&arena, &completions, node, handle, config); } try send(types.Response{ @@ -829,7 +832,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v } // Semantic highlighting else if (std.mem.eql(u8, method, "textDocument/semanticTokens")) { - // @TODO Implement this (we dont get here from vscode atm even when we get the client capab.) + // TODO Implement this (we dont get here from vscode atm even when we get the client capab.) return try respondGeneric(id, empty_array_response); } // Autocomplete / Signatures From 545989339ddb0a2ad9a2877711bc80bba7a4b781 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 22:05:11 +0300 Subject: [PATCH 06/14] Remove unused code, fixed snippet self param skipping --- src/analysis.zig | 90 ++---------------------------------------------- src/main.zig | 45 ++++++++++++++++-------- 2 files changed, 33 insertions(+), 102 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index e5264e6..3c5ec53 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -181,25 +181,6 @@ fn getDeclName(tree: *ast.Tree, node: *ast.Node) ?[]const u8 { }; } -/// Gets the child of node -pub fn getChild(tree: *ast.Tree, node: *ast.Node, name: []const u8) ?*ast.Node { - var child_idx: usize = 0; - while (node.iterate(child_idx)) |child| : (child_idx += 1) { - const child_name = getDeclName(tree, child) orelse continue; - if (std.mem.eql(u8, child_name, name)) return child; - } - return null; -} - -/// Gets the child of slice -pub fn getChildOfSlice(tree: *ast.Tree, nodes: []*ast.Node, name: []const u8) ?*ast.Node { - for (nodes) |child| { - const child_name = getDeclName(tree, child) orelse continue; - if (std.mem.eql(u8, child_name, name)) return child; - } - return null; -} - fn findReturnStatementInternal( tree: *ast.Tree, fn_decl: *ast.Node.FnProto, @@ -378,22 +359,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, const decl = (try resolveTypeOfNode(store, arena, .{ .node = call.lhs, .handle = handle })) orelse return null; if (decl.node.cast(ast.Node.FnProto)) |fn_decl| { - // @TODO use BoundTypeParams: ParamDecl -> NodeWithHandle or something - // Add type param values to the scope nodes - // const param_len = std.math.min(call.params_len, fn_decl.params_len); - // for (fn_decl.paramsConst()) |decl_param, param_idx| { - // if (param_idx >= param_len) break; - // if (decl_param.name_token == null) continue; - // const type_param = switch (decl_param.param_type) { - // .type_expr => |type_node| if (type_node.cast(ast.Node.Identifier)) |ident| - // std.mem.eql(u8, analysis_ctx.tree().tokenSlice(ident.token), "type") - // else - // false, - // else => false, - // }; - // if (!type_param) continue; - // const call_param_type = (try resolveTypeOfNode(store, arena, .{ .node = call.paramsConst()[param_idx], .handle = handle })) orelse continue; - // } + // TODO Use some type of ParamDecl -> NodeWithHandle map while resolving, and associate type params here. return try resolveReturnType(store, arena, fn_decl, decl.handle); } return decl; @@ -726,57 +692,6 @@ pub fn nodeToString(tree: *ast.Tree, node: *ast.Node) ?[]const u8 { return null; } -fn makeAccessNode(allocator: *std.mem.Allocator, expr: *ast.Node) !*ast.Node { - const suffix_op_node = try allocator.create(ast.Node.SuffixOp); - suffix_op_node.* = .{ - .op = .{ .ArrayAccess = expr }, - .lhs = expr, - .rtoken = expr.lastToken(), - }; - return &suffix_op_node.base; -} - -fn makeUnwrapNode(allocator: *std.mem.Allocator, expr: *ast.Node) !*ast.Node { - const suffix_op_node = try allocator.create(ast.Node.SuffixOp); - suffix_op_node.* = .{ - .op = .UnwrapOptional, - .lhs = expr, - .rtoken = expr.lastToken(), - }; - return &suffix_op_node.base; -} - -fn makeVarDeclNode( - allocator: *std.mem.Allocator, - doc: ?*ast.Node.DocComment, - comptime_token: ?ast.TokenIndex, - name_token: ast.TokenIndex, - type_expr: ?*ast.Node, - init_expr: ?*ast.Node, -) !*ast.Node { - // TODO: This will not be needed anymore when we use out own decl type instead of directly - // repurposing ast nodes. - const var_decl_node = try allocator.create(ast.Node.VarDecl); - var_decl_node.* = .{ - .doc_comments = doc, - .comptime_token = comptime_token, - .visib_token = null, - .thread_local_token = null, - .name_token = name_token, - .eq_token = null, - .mut_token = name_token, - .extern_export_token = null, - .lib_name = null, - .type_node = type_expr, - .align_node = null, - .section_node = null, - .init_node = init_expr, - .semicolon_token = name_token, - }; - - return &var_decl_node.base; -} - fn nodeContainsSourceIndex(tree: *ast.Tree, node: *ast.Node, source_index: usize) bool { const first_token = tree.token_locs[node.firstToken()]; const last_token = tree.token_locs[node.lastToken()]; @@ -1265,7 +1180,8 @@ fn nodeSourceRange(tree: *ast.Tree, node: *ast.Node) SourceRange { // TODO Make enum and error stores per-document // CLear the doc ones before calling this and // rebuild them here. - +// TODO Possibly collect all imports to diff them on changes +// as well fn makeScopeInternal( allocator: *std.mem.Allocator, scopes: *std.ArrayList(Scope), diff --git a/src/main.zig b/src/main.zig index 8a84917..a9786df 100644 --- a/src/main.zig +++ b/src/main.zig @@ -204,7 +204,6 @@ fn containerToCompletion( config: Config, ) !void { // @TODO Something like iterateSymbolsGlobal but for container to support uses. - const container = container_handle.node; const handle = container_handle.handle; @@ -272,20 +271,36 @@ fn nodeToCompletion( const use_snippets = config.enable_snippets and client_capabilities.supports_snippets; const insert_text = if (use_snippets) blk: { - // @TODO Rebuild this. - const skip_self_param = false; - // const skip_self_param = if (func.params_len > 0) param_check: { - // break :param_check switch (func.paramsConst()[0].param_type) { - // .type_expr => |type_node| if (analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, type_node)) - // true - // else if (type_node.cast(std.zig.ast.Node.PrefixOp)) |prefix_op| - // prefix_op.op == .PtrType and analysis_ctx.in_container == analysis.resolveTypeOfNode(&child_analysis_ctx, prefix_op.rhs) - // else - // false, - // else => false, - // }; - // } else - // false; + const skip_self_param = if (func.params_len > 0) param_check: { + const in_container = analysis.innermostContainer(handle, handle.tree.token_locs[func.firstToken()].start); + switch (func.paramsConst()[0].param_type) { + .type_expr => |type_node| { + if (try analysis.resolveTypeOfNode(&document_store, arena, .{ + .node = type_node, + .handle = handle, + })) |resolved_type| { + if (in_container.node == resolved_type.node) + break :param_check true; + } + + if (type_node.cast(std.zig.ast.Node.PrefixOp)) |prefix_op| { + if (prefix_op.op == .PtrType) { + if (try analysis.resolveTypeOfNode(&document_store, arena, .{ + .node = prefix_op.rhs, + .handle = handle, + })) |resolved_prefix_op| { + if (in_container.node == resolved_prefix_op.node) + break :param_check true; + } + } + } + + break :param_check false; + }, + else => break :param_check false, + } + } else + false; break :blk try analysis.getFunctionSnippet(&arena.allocator, handle.tree, func, skip_self_param); } else From 86e417e8dc95b35f13d28fef3e55d78b2b38c10f Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 10 Jun 2020 22:52:33 +0300 Subject: [PATCH 07/14] Add hover, goto, completions for alternate declaration types --- src/analysis.zig | 2 +- src/main.zig | 101 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 3c5ec53..90dc34d 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -30,7 +30,7 @@ pub fn getDocComments(allocator: *std.mem.Allocator, tree: *ast.Tree, node: *ast return null; } -fn collectDocComments(allocator: *std.mem.Allocator, tree: *ast.Tree, doc_comments: *ast.Node.DocComment) ![]const u8 { +pub fn collectDocComments(allocator: *std.mem.Allocator, tree: *ast.Tree, doc_comments: *ast.Node.DocComment) ![]const u8 { var lines = std.ArrayList([]const u8).init(allocator); defer lines.deinit(); diff --git a/src/main.zig b/src/main.zig index a9786df..752c9f6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -421,9 +421,11 @@ fn gotoDefinitionSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, de } fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle: analysis.DeclWithHandle) !void { - switch (decl_handle.decl.*) { - .ast_node => |node| { - const result = try resolveVarDeclFnAlias(arena, .{ .node = node, .handle = decl_handle.handle }); + const handle = decl_handle.handle; + + const md_string = switch (decl_handle.decl.*) { + .ast_node => |node| ast_node: { + const result = try resolveVarDeclFnAlias(arena, .{ .node = node, .handle = handle }); const doc_str = if (try analysis.getDocComments(&arena.allocator, result.handle.tree, result.node)) |str| str @@ -442,19 +444,48 @@ fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle else => analysis.nodeToString(result.handle.tree, result.node) orelse return try respondGeneric(id, null_result_response), }; - const md_string = try std.fmt.allocPrint(&arena.allocator, "```zig\n{}\n```\n{}", .{ signature_str, doc_str }); - try send(types.Response{ - .id = id, - .result = .{ - .Hover = .{ - .contents = .{ .value = md_string }, - }, - }, - }); + break :ast_node try std.fmt.allocPrint(&arena.allocator, "```zig\n{}\n```\n{}", .{ signature_str, doc_str }); }, - // @TODO Rest of decls - else => return try respondGeneric(id, null_result_response), - } + .param_decl => |param| param_decl: { + const doc_str = if (param.doc_comments) |doc_comments| + try analysis.collectDocComments(&arena.allocator, handle.tree, doc_comments) + else + ""; + + break :param_decl try std.fmt.allocPrint( + &arena.allocator, + "```zig\n{}\n```\n{}", + .{ + handle.tree.source[handle.tree.token_locs[param.firstToken()].start..handle.tree.token_locs[param.lastToken()].end], + doc_str, + }, + ); + }, + .pointer_payload => |payload| try std.fmt.allocPrint( + &arena.allocator, + "```zig\n{}\n```", + .{handle.tree.tokenSlice(payload.node.value_symbol.firstToken())}, + ), + .array_payload => |payload| try std.fmt.allocPrint( + &arena.allocator, + "```zig\n{}\n```", + .{handle.tree.tokenSlice(payload.identifier.firstToken())}, + ), + .switch_payload => |payload| try std.fmt.allocPrint( + &arena.allocator, + "```zig\n{}\n```", + .{handle.tree.tokenSlice(payload.node.value_symbol.firstToken())}, + ), + }; + + try send(types.Response{ + .id = id, + .result = .{ + .Hover = .{ + .contents = .{ .value = md_string }, + }, + }, + }); } fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: *DocumentStore.Handle) !?analysis.DeclWithHandle { @@ -563,12 +594,44 @@ const DeclToCompletionContext = struct { }; fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { + const tree = decl_handle.handle.tree; + switch (decl_handle.decl.*) { - .ast_node => |node| { - try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, context.config.*); + .ast_node => |node| try nodeToCompletion(context.arena, context.completions, .{ .node = node, .handle = decl_handle.handle }, context.orig_handle, context.config.*), + .param_decl => |param| { + const doc = if (param.doc_comments) |doc_comments| + types.MarkupContent{ + .kind = .Markdown, + .value = try analysis.collectDocComments(&context.arena.allocator, tree, doc_comments), + } + else + null; + + try context.completions.append(.{ + .label = tree.tokenSlice(param.name_token.?), + .kind = .Constant, + .documentation = doc, + .detail = tree.source[tree.token_locs[param.firstToken()].start..tree.token_locs[param.lastToken()].end], + }); + }, + .pointer_payload => |payload| { + try context.completions.append(.{ + .label = tree.tokenSlice(payload.node.value_symbol.firstToken()), + .kind = .Variable, + }); + }, + .array_payload => |payload| { + try context.completions.append(.{ + .label = tree.tokenSlice(payload.identifier.firstToken()), + .kind = .Variable, + }); + }, + .switch_payload => |payload| { + try context.completions.append(.{ + .label = tree.tokenSlice(payload.node.value_symbol.firstToken()), + .kind = .Variable, + }); }, - else => {}, - // @TODO The rest } } From b8b6c534e8e740b972542a0f1b16605f1dc18518 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 00:24:57 +0300 Subject: [PATCH 08/14] Fixed field access --- src/analysis.zig | 3 ++- src/main.zig | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 90dc34d..80c40a5 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -555,6 +555,7 @@ pub fn getFieldAccessTypeNode( store: *DocumentStore, arena: *std.heap.ArenaAllocator, handle: *DocumentStore.Handle, + source_index: usize, tokenizer: *std.zig.Tokenizer, ) !?NodeWithHandle { var current_node = NodeWithHandle{ @@ -567,7 +568,7 @@ pub fn getFieldAccessTypeNode( switch (tok.id) { .Eof => return try resolveFieldAccessLhsType(store, arena, current_node), .Identifier => { - if (try lookupSymbolGlobal(store, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], tok.loc.start)) |child| { + if (try lookupSymbolGlobal(store, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], source_index)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, diff --git a/src/main.zig b/src/main.zig index 752c9f6..c2cc6e4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -525,7 +525,7 @@ fn getSymbolFieldAccess( const line = try handle.document.getLine(@intCast(usize, position.line)); var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); - if (try analysis.getFieldAccessTypeNode(&document_store, arena, handle, &tokenizer)) |container_handle| { + if (try analysis.getFieldAccessTypeNode(&document_store, arena, handle, pos_index, &tokenizer)) |container_handle| { return try analysis.lookupSymbolContainer(&document_store, container_handle, name, true); } return null; @@ -670,7 +670,8 @@ fn completeFieldAccess(id: types.RequestId, handle: *DocumentStore.Handle, posit const line = try handle.document.getLine(@intCast(usize, position.line)); var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); - if (try analysis.getFieldAccessTypeNode(&document_store, &arena, handle, &tokenizer)) |node| { + const pos_index = try handle.document.positionToIndex(position); + if (try analysis.getFieldAccessTypeNode(&document_store, &arena, handle, pos_index, &tokenizer)) |node| { try nodeToCompletion(&arena, &completions, node, handle, config); } From 7650decfa0d45aa0671ae7114be520c3908a9475 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 01:00:13 +0300 Subject: [PATCH 09/14] Improvements --- src/analysis.zig | 35 ++++++++++++++++++++--------------- src/main.zig | 13 +++++++++++++ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 80c40a5..877be16 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -121,6 +121,13 @@ pub fn getVariableSignature(tree: *ast.Tree, var_decl: *ast.Node.VarDecl) []cons return tree.source[start..end]; } +// analysis.getContainerFieldSignature(handle.tree, field) +pub fn getContainerFieldSignature(tree: *ast.Tree, field: *ast.Node.ContainerField) []const u8 { + const start = tree.token_locs[field.firstToken()].start; + const end = tree.token_locs[field.lastToken()].end; + return tree.source[start..end]; +} + pub fn isTypeFunction(tree: *ast.Tree, func: *ast.Node.FnProto) bool { switch (func.return_type) { .Explicit => |node| return if (node.cast(std.zig.ast.Node.Identifier)) |ident| @@ -158,7 +165,7 @@ pub fn getDeclNameToken(tree: *ast.Tree, node: *ast.Node) ?ast.TokenIndex { const field = node.cast(ast.Node.ContainerField).?; return field.name_token; }, - // We need identifier for captures + // We need identifier for captures and error set tags .Identifier => { const ident = node.cast(ast.Node.Identifier).?; return ident.token; @@ -402,9 +409,6 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, })) orelse return null, ); - // @TODO Error sets - if (left_type.node.id != .ContainerDecl and left_type.node.id != .Root) return null; - if (try lookupSymbolContainer(store, left_type, rhs_str, true)) |child| { return try child.resolveType(store, arena); } else return null; @@ -580,12 +584,6 @@ pub fn getFieldAccessTypeNode( if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, arena, current_node); current_node = try resolveFieldAccessLhsType(store, arena, current_node); - // @TODO Error sets - if (current_node.node.id != .ContainerDecl and current_node.node.id != .Root) { - // @TODO Is this ok? - return null; - } - if (try lookupSymbolContainer(store, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; @@ -1077,7 +1075,11 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHandle, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { const container = container_handle.node; const handle = container_handle.handle; - std.debug.assert(container.id == .ContainerDecl or container.id == .Root); + + if (container.id != .ContainerDecl and container.id != .Root and container.id != .ErrorSetDecl) { + return null; + } + // Find the container scope. var maybe_container_scope: ?*Scope = null; for (handle.document_scope.scopes) |*scope| { @@ -1094,7 +1096,7 @@ pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHa if (container_scope.decls.get(symbol)) |candidate| { switch (candidate.value) { .ast_node => |node| { - if (node.id == .ContainerDecl and !accept_fields) return null; + if (node.id == .ContainerField and !accept_fields) return null; }, else => {}, } @@ -1106,7 +1108,9 @@ pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHa } return null; } - unreachable; + + std.debug.warn("Did not find container scope when looking up in container {} (name: {})\n", .{container, getDeclName(handle.tree, container)}); + return null; } pub const DocumentScope = struct { @@ -1147,7 +1151,7 @@ pub const DocumentScope = struct { pub const Scope = struct { pub const Data = union(enum) { - container: *ast.Node, // .id is ContainerDecl or Root + container: *ast.Node, // .id is ContainerDecl or Root or ErrorSetDecl function: *ast.Node, // .id is FnProto block: *ast.Node, // .id is Block other, @@ -1189,10 +1193,11 @@ fn makeScopeInternal( tree: *ast.Tree, node: *ast.Node, ) error{OutOfMemory}!void { - if (node.id == .Root or node.id == .ContainerDecl) { + if (node.id == .Root or node.id == .ContainerDecl or node.id == .ErrorSetDecl) { const ast_decls = switch (node.id) { .ContainerDecl => node.cast(ast.Node.ContainerDecl).?.fieldsAndDeclsConst(), .Root => node.cast(ast.Node.Root).?.declsConst(), + .ErrorSetDecl => node.cast(ast.Node.ErrorSetDecl).?.declsConst(), else => unreachable, }; diff --git a/src/main.zig b/src/main.zig index c2cc6e4..f56e48f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -334,6 +334,15 @@ fn nodeToCompletion( .detail = analysis.getVariableSignature(handle.tree, var_decl), }); }, + .ContainerField => { + const field = node.cast(std.zig.ast.Node.ContainerField).?; + try list.append(.{ + .label = handle.tree.tokenSlice(field.name_token), + .kind = .Field, + .documentation = doc, + .detail = analysis.getContainerFieldSignature(handle.tree, field), + }); + }, .PrefixOp => { const prefix_op = node.cast(std.zig.ast.Node.PrefixOp).?; switch (prefix_op.op) { @@ -441,6 +450,10 @@ fn hoverSymbol(id: types.RequestId, arena: *std.heap.ArenaAllocator, decl_handle const fn_decl = result.node.cast(std.zig.ast.Node.FnProto).?; break :blk analysis.getFunctionSignature(result.handle.tree, fn_decl); }, + .ContainerField => blk: { + const field = node.cast(std.zig.ast.Node.ContainerField).?; + break :blk analysis.getContainerFieldSignature(result.handle.tree, field); + }, else => analysis.nodeToString(result.handle.tree, result.node) orelse return try respondGeneric(id, null_result_response), }; From 323b05856e28646e1366019299fb0978fa859cce Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 01:09:28 +0300 Subject: [PATCH 10/14] Fixed array payload type resolution --- src/analysis.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 877be16..8b2a209 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -994,10 +994,10 @@ pub const DeclWithHandle = struct { .array_payload => |pay| try resolveBracketAccessType( store, arena, - .{ + (try resolveTypeOfNode(store, arena, .{ .node = pay.array_expr, .handle = self.handle, - }, + })) orelse return null, .Single, ), // TODO Resolve switch payload types From 80dd19dd8b5d25124acb1d73a3a502d259f27cb7 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 02:40:11 +0300 Subject: [PATCH 11/14] Usingnamespace support --- src/analysis.zig | 157 ++++++++++++++++++++++++++++++++++------------- src/main.zig | 36 ++++------- 2 files changed, 126 insertions(+), 67 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 8b2a209..b8ae5f9 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -265,6 +265,16 @@ fn resolveUnwrapOptionalType(store: *DocumentStore, arena: *std.heap.ArenaAlloca return null; } +fn resolveUnwrapErrorType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, rhs: NodeWithHandle) !?NodeWithHandle { + if (rhs.node.cast(ast.Node.InfixOp)) |infix_op| { + if (infix_op.op == .ErrorUnion) { + return try resolveTypeOfNode(store, arena, .{ .node = infix_op.rhs, .handle = rhs.handle }); + } + } + + return null; +} + /// Resolves the child type of a defer type fn resolveDerefType(store: *DocumentStore, arena: *std.heap.ArenaAllocator, deref: NodeWithHandle) !?NodeWithHandle { if (deref.node.cast(ast.Node.PrefixOp)) |pop| { @@ -352,7 +362,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, return try resolveTypeOfNode(store, arena, .{ .node = vari.type_node orelse vari.init_node.?, .handle = handle }); }, .Identifier => { - if (try lookupSymbolGlobal(store, handle, handle.tree.getNodeSource(node), handle.tree.token_locs[node.firstToken()].start)) |child| { + if (try lookupSymbolGlobal(store, arena, handle, handle.tree.getNodeSource(node), handle.tree.token_locs[node.firstToken()].start)) |child| { return try child.resolveType(store, arena); } return null; @@ -409,7 +419,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, })) orelse return null, ); - if (try lookupSymbolContainer(store, left_type, rhs_str, true)) |child| { + if (try lookupSymbolContainer(store, arena, left_type, rhs_str, true)) |child| { return try child.resolveType(store, arena); } else return null; }, @@ -420,6 +430,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, })) orelse return null; return try resolveUnwrapOptionalType(store, arena, left_type); }, + .ErrorUnion => return node_handle, else => return null, } }, @@ -433,17 +444,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, => return node_handle, .Try => { const rhs_type = (try resolveTypeOfNode(store, arena, .{ .node = prefix_op.rhs, .handle = handle })) orelse return null; - switch (rhs_type.node.id) { - .InfixOp => { - const infix_op = rhs_type.node.cast(ast.Node.InfixOp).?; - if (infix_op.op == .ErrorUnion) return NodeWithHandle{ - .node = infix_op.rhs, - .handle = rhs_type.handle, - }; - }, - else => {}, - } - return rhs_type; + return try resolveUnwrapErrorType(store, arena, rhs_type); }, else => {}, } @@ -572,7 +573,7 @@ pub fn getFieldAccessTypeNode( switch (tok.id) { .Eof => return try resolveFieldAccessLhsType(store, arena, current_node), .Identifier => { - if (try lookupSymbolGlobal(store, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], source_index)) |child| { + if (try lookupSymbolGlobal(store, arena, current_node.handle, tokenizer.buffer[tok.loc.start..tok.loc.end], source_index)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, @@ -584,7 +585,7 @@ pub fn getFieldAccessTypeNode( if (after_period.loc.end == tokenizer.buffer.len) return try resolveFieldAccessLhsType(store, arena, current_node); current_node = try resolveFieldAccessLhsType(store, arena, current_node); - if (try lookupSymbolContainer(store, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { + if (try lookupSymbolContainer(store, arena, current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end], true)) |child| { current_node = (try child.resolveType(store, arena)) orelse return null; } else return null; }, @@ -976,6 +977,13 @@ pub const DeclWithHandle = struct { }; } + fn isPublic(self: DeclWithHandle) bool { + return switch (self.decl.*) { + .ast_node => |node| isNodePublic(self.handle.tree, node), + else => true, + }; + } + fn resolveType(self: DeclWithHandle, store: *DocumentStore, arena: *std.heap.ArenaAllocator) !?NodeWithHandle { return switch (self.decl.*) { .ast_node => |node| try resolveTypeOfNode(store, arena, .{ .node = node, .handle = self.handle }), @@ -1006,13 +1014,65 @@ pub const DeclWithHandle = struct { } }; +fn findContainerScope(container_handle: NodeWithHandle) ?*Scope { + const container = container_handle.node; + const handle = container_handle.handle; + + if (container.id != .ContainerDecl and container.id != .Root and container.id != .ErrorSetDecl) { + return null; + } + + // Find the container scope. + var container_scope: ?*Scope = null; + for (handle.document_scope.scopes) |*scope| { + switch (scope.*.data) { + .container => |node| if (node == container) { + container_scope = scope; + break; + }, + else => {}, + } + } + return container_scope; +} + +pub fn iterateSymbolsContainer( + store: *DocumentStore, + arena: *std.heap.ArenaAllocator, + container_handle: NodeWithHandle, + orig_handle: *DocumentStore.Handle, + comptime callback: var, + context: var, +) error{OutOfMemory}!void { + const container = container_handle.node; + const handle = container_handle.handle; + + if (findContainerScope(container_handle)) |container_scope| { + var decl_it = container_scope.decls.iterator(); + while (decl_it.next()) |entry| { + const decl = DeclWithHandle{ .decl = &entry.value, .handle = handle }; + if (handle != orig_handle and !decl.isPublic()) continue; + try callback(context, decl); + } + + for (container_scope.uses) |use| { + if (handle != orig_handle and use.visib_token == null) continue; + const use_expr = (try resolveTypeOfNode(store, arena, .{ .node = use.expr, .handle = handle })) orelse continue; + try iterateSymbolsContainer(store, arena, use_expr, orig_handle, callback, context); + } + } + + std.debug.warn("Did not find container scope when iterating container {} (name: {})\n", .{ container, getDeclName(handle.tree, container) }); +} + pub fn iterateSymbolsGlobal( store: *DocumentStore, + arena: *std.heap.ArenaAllocator, handle: *DocumentStore.Handle, source_index: usize, comptime callback: var, context: var, -) !void { +) error{OutOfMemory}!void { for (handle.document_scope.scopes) |scope| { if (source_index >= scope.range.start and source_index < scope.range.end) { var decl_it = scope.decls.iterator(); @@ -1022,6 +1082,8 @@ pub fn iterateSymbolsGlobal( for (scope.uses) |use| { // @TODO Resolve uses, iterate over their symbols. + const use_expr = (try resolveTypeOfNode(store, arena, .{ .node = use.expr, .handle = handle })) orelse continue; + try iterateSymbolsContainer(store, arena, use_expr, handle, callback, context); } } @@ -1045,7 +1107,32 @@ pub fn innermostContainer(handle: *DocumentStore.Handle, source_index: usize) No return .{ .node = current, .handle = handle }; } -pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, symbol: []const u8, source_index: usize) !?DeclWithHandle { +fn resolveUse( + store: *DocumentStore, + arena: *std.heap.ArenaAllocator, + uses: []const *ast.Node.Use, + symbol: []const u8, + handle: *DocumentStore.Handle, +) error{OutOfMemory}!?DeclWithHandle { + for (uses) |use| { + const use_expr = (try resolveTypeOfNode(store, arena, .{ .node = use.expr, .handle = handle })) orelse continue; + if (try lookupSymbolContainer(store, arena, use_expr, symbol, false)) |candidate| { + if (candidate.handle != handle and !candidate.isPublic()) { + continue; + } + return candidate; + } + } + return null; +} + +pub fn lookupSymbolGlobal( + store: *DocumentStore, + arena: *std.heap.ArenaAllocator, + handle: *DocumentStore.Handle, + symbol: []const u8, + source_index: usize, +) error{OutOfMemory}!?DeclWithHandle { for (handle.document_scope.scopes) |scope| { if (source_index >= scope.range.start and source_index < scope.range.end) { if (scope.decls.get(symbol)) |candidate| { @@ -1061,9 +1148,7 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, }; } - for (scope.uses) |use| { - // @TODO Resolve use, lookup symbol in resulting scope. - } + if (try resolveUse(store, arena, scope.uses, symbol, handle)) |result| return result; } if (scope.range.start > source_index) return null; @@ -1072,27 +1157,17 @@ pub fn lookupSymbolGlobal(store: *DocumentStore, handle: *DocumentStore.Handle, return null; } -pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHandle, symbol: []const u8, accept_fields: bool) !?DeclWithHandle { +pub fn lookupSymbolContainer( + store: *DocumentStore, + arena: *std.heap.ArenaAllocator, + container_handle: NodeWithHandle, + symbol: []const u8, + accept_fields: bool, +) error{OutOfMemory}!?DeclWithHandle { const container = container_handle.node; const handle = container_handle.handle; - if (container.id != .ContainerDecl and container.id != .Root and container.id != .ErrorSetDecl) { - return null; - } - - // Find the container scope. - var maybe_container_scope: ?*Scope = null; - for (handle.document_scope.scopes) |*scope| { - switch (scope.*.data) { - .container => |node| if (node == container) { - maybe_container_scope = scope; - break; - }, - else => {}, - } - } - - if (maybe_container_scope) |container_scope| { + if (findContainerScope(container_handle)) |container_scope| { if (container_scope.decls.get(symbol)) |candidate| { switch (candidate.value) { .ast_node => |node| { @@ -1103,13 +1178,11 @@ pub fn lookupSymbolContainer(store: *DocumentStore, container_handle: NodeWithHa return DeclWithHandle{ .decl = &candidate.value, .handle = handle }; } - for (container_scope.uses) |use| { - // @TODO Resolve use, lookup symbol in resulting scope. - } + if (try resolveUse(store, arena, container_scope.uses, symbol, handle)) |result| return result; return null; } - std.debug.warn("Did not find container scope when looking up in container {} (name: {})\n", .{container, getDeclName(handle.tree, container)}); + std.debug.warn("Did not find container scope when looking up in container {} (name: {})\n", .{ container, getDeclName(handle.tree, container) }); return null; } diff --git a/src/main.zig b/src/main.zig index f56e48f..5678a2c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -196,26 +196,6 @@ fn publishDiagnostics(handle: DocumentStore.Handle, config: Config) !void { }); } -fn containerToCompletion( - arena: *std.heap.ArenaAllocator, - list: *std.ArrayList(types.CompletionItem), - container_handle: analysis.NodeWithHandle, - orig_handle: *DocumentStore.Handle, - config: Config, -) !void { - // @TODO Something like iterateSymbolsGlobal but for container to support uses. - const container = container_handle.node; - const handle = container_handle.handle; - - 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 == handle or analysis.isNodePublic(handle.tree, child_node)) { - try nodeToCompletion(arena, list, .{ .node = child_node, .handle = handle }, orig_handle, config); - } - } -} - fn resolveVarDeclFnAlias(arena: *std.heap.ArenaAllocator, decl_handle: analysis.NodeWithHandle) !analysis.NodeWithHandle { const decl = decl_handle.node; const handle = decl_handle.handle; @@ -263,7 +243,13 @@ fn nodeToCompletion( switch (node.id) { .ErrorSetDecl, .Root, .ContainerDecl => { - try containerToCompletion(arena, list, node_handle, orig_handle, config); + const context = DeclToCompletionContext{ + .completions = list, + .config = &config, + .arena = arena, + .orig_handle = orig_handle, + }; + try analysis.iterateSymbolsContainer(&document_store, arena, node_handle, orig_handle, declToCompletion, context); }, .FnProto => { const func = node.cast(std.zig.ast.Node.FnProto).?; @@ -505,7 +491,7 @@ fn getSymbolGlobal(arena: *std.heap.ArenaAllocator, pos_index: usize, handle: *D const name = identifierFromPosition(pos_index, handle.*); if (name.len == 0) return null; - return try analysis.lookupSymbolGlobal(&document_store, handle, name, pos_index); + return try analysis.lookupSymbolGlobal(&document_store, arena, handle, name, pos_index); } fn gotoDefinitionGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore.Handle, config: Config) !void { @@ -539,7 +525,7 @@ fn getSymbolFieldAccess( var tokenizer = std.zig.Tokenizer.init(line[range.start..range.end]); if (try analysis.getFieldAccessTypeNode(&document_store, arena, handle, pos_index, &tokenizer)) |container_handle| { - return try analysis.lookupSymbolContainer(&document_store, container_handle, name, true); + return try analysis.lookupSymbolContainer(&document_store, arena, container_handle, name, true); } return null; } @@ -606,7 +592,7 @@ const DeclToCompletionContext = struct { orig_handle: *DocumentStore.Handle, }; -fn decltoCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { +fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { const tree = decl_handle.handle.tree; switch (decl_handle.decl.*) { @@ -661,7 +647,7 @@ fn completeGlobal(id: types.RequestId, pos_index: usize, handle: *DocumentStore. .arena = &arena, .orig_handle = handle, }; - try analysis.iterateSymbolsGlobal(&document_store, handle, pos_index, decltoCompletion, context); + try analysis.iterateSymbolsGlobal(&document_store, &arena, handle, pos_index, declToCompletion, context); try send(types.Response{ .id = id, From 28e9d08f03b4f6644f917cebff2837c9c590afe0 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 03:20:44 +0300 Subject: [PATCH 12/14] Stop adding malformed container decls into scopes --- src/analysis.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index b8ae5f9..4d8033b 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1081,7 +1081,6 @@ pub fn iterateSymbolsGlobal( } for (scope.uses) |use| { - // @TODO Resolve uses, iterate over their symbols. const use_expr = (try resolveTypeOfNode(store, arena, .{ .node = use.expr, .handle = handle })) orelse continue; try iterateSymbolsContainer(store, arena, use_expr, handle, callback, context); } @@ -1304,6 +1303,12 @@ fn makeScopeInternal( continue; } + if (decl.cast(ast.Node.ContainerField)) |field| { + if (field.type_expr == null and field.value_expr == null) { + continue; + } + } + if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = decl })) |existing| { // TODO Record a redefinition error. } From 693e810d751657e374826f767ee99a8408d525b1 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 11:12:31 +0300 Subject: [PATCH 13/14] Resovle parens, only skip empty container fields where we have to --- src/analysis.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index 4d8033b..b6567f1 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -381,6 +381,10 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, } return decl; }, + .GroupedExpression => { + const grouped = node.cast(ast.Node.GroupedExpression).?; + return try resolveTypeOfNode(store, arena, .{ .node = grouped.expr, .handle = handle }); + }, .StructInitializer => { const struct_init = node.cast(ast.Node.StructInitializer).?; return try resolveTypeOfNode(store, arena, .{ .node = struct_init.lhs, .handle = handle }); @@ -430,6 +434,13 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator, })) orelse return null; return try resolveUnwrapOptionalType(store, arena, left_type); }, + .Catch => { + const left_type = (try resolveTypeOfNode(store, arena, .{ + .node = infix_op.lhs, + .handle = handle, + })) orelse return null; + return try resolveUnwrapErrorType(store, arena, left_type); + }, .ErrorUnion => return node_handle, else => return null, } @@ -1305,7 +1316,13 @@ fn makeScopeInternal( if (decl.cast(ast.Node.ContainerField)) |field| { if (field.type_expr == null and field.value_expr == null) { - continue; + if (node.id == .Root) continue; + if (node.cast(ast.Node.ContainerDecl)) |container| { + const kind = tree.token_ids[container.kind_token]; + if (kind == .Keyword_struct or (kind == .Keyword_union and container.init_arg_expr == .None)) { + continue; + } + } } } From be9f2de43a1386b3c38714bdeba68eea7167794c Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Thu, 11 Jun 2020 11:21:08 +0300 Subject: [PATCH 14/14] Fixed error union decls --- src/analysis.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/analysis.zig b/src/analysis.zig index b6567f1..4151815 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -165,6 +165,10 @@ pub fn getDeclNameToken(tree: *ast.Tree, node: *ast.Node) ?ast.TokenIndex { const field = node.cast(ast.Node.ContainerField).?; return field.name_token; }, + .ErrorTag => { + const tag = node.cast(ast.Node.ErrorTag).?; + return tag.name_token; + }, // We need identifier for captures and error set tags .Identifier => { const ident = node.cast(ast.Node.Identifier).?; @@ -1291,7 +1295,7 @@ fn makeScopeInternal( .tests = &[0]*ast.Node{}, .data = .{ .container = node }, }; - var scope_idx = scopes.items.len - 1; + const scope_idx = scopes.items.len - 1; var uses = std.ArrayList(*ast.Node.Use).init(allocator); var tests = std.ArrayList(*ast.Node).init(allocator);