Skip to content

Commit f9e6c82

Browse files
authored
psi: optimize AST traversal and fix tree-sitter resource leaks (#178)
1 parent 052449d commit f9e6c82

28 files changed

+357
-100
lines changed

src/analyzer/index/IndexingRoot.v

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,9 @@ pub fn (mut i IndexingRoot) index() BuiltIndexStatus {
178178
return .from_scratch
179179
}
180180

181-
pub fn (mut i IndexingRoot) index_file(path string, content string) !FileIndex {
181+
pub fn (mut i IndexingRoot) index_file(path string, content string, mut p parser.Parser) !FileIndex {
182182
last_modified := os.file_last_mod_unix(path)
183-
res := parser.parse_code(content)
183+
res := p.parse_code(content)
184184
psi_file := psi.new_psi_file(path, res.tree, content)
185185
module_fqn := psi.module_qualified_name(psi_file, i.root)
186186

@@ -221,6 +221,8 @@ pub fn (mut i IndexingRoot) spawn_indexing_workers(cache_chan chan FileIndex, fi
221221
wg.add(workers)
222222
for j := 0; j < workers; j++ {
223223
spawn fn [file_chan, mut wg, mut i, cache_chan] () {
224+
mut p := parser.Parser.new()
225+
defer { p.free() }
224226
for {
225227
filepath := <-file_chan or { break }
226228
content := os.read_file(filepath) or {
@@ -230,7 +232,7 @@ pub fn (mut i IndexingRoot) spawn_indexing_workers(cache_chan chan FileIndex, fi
230232
}).error('Error reading file for index')
231233
continue
232234
}
233-
cache_chan <- i.index_file(filepath, content) or {
235+
cache_chan <- i.index_file(filepath, content, mut p) or {
234236
loglib.with_fields({
235237
'uri': lsp.document_uri_from_path(filepath).str()
236238
'error': err.str()
@@ -303,7 +305,10 @@ pub fn (mut i IndexingRoot) mark_as_dirty(filepath string, new_content string) !
303305
'uri': lsp.document_uri_from_path(filepath).str()
304306
}).info('Marking document as dirty')
305307
i.index.per_file.data.delete(filepath)
306-
res := i.index_file(filepath, new_content) or {
308+
309+
mut p := parser.Parser.new()
310+
defer { p.free() }
311+
res := i.index_file(filepath, new_content, mut p) or {
307312
return error('Error indexing dirty ${filepath}: ${err}')
308313
}
309314
i.index.per_file.data[filepath] = res
@@ -321,7 +326,9 @@ pub fn (mut i IndexingRoot) add_file(filepath string, content string) !FileIndex
321326
'uri': lsp.document_uri_from_path(filepath).str()
322327
}).info('Adding new document')
323328

324-
res := i.index_file(filepath, content) or {
329+
mut p := parser.Parser.new()
330+
defer { p.free() }
331+
res := i.index_file(filepath, content, mut p) or {
325332
return error('Error indexing added ${filepath}: ${err}')
326333
}
327334
i.index.per_file.data[filepath] = res

src/analyzer/index/StubTree.v

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,56 @@ pub fn (tree &StubTree) get_imported_modules() []string {
5555
}
5656

5757
pub fn build_stub_tree(file &psi.PsiFile, indexing_root string) &StubTree {
58-
root := file.root()
58+
mut walker := psi.new_tree_walker(file.tree.root_node())
59+
defer { walker.free() }
60+
5961
stub_root := psi.new_root_stub(file.path())
6062
module_fqn := psi.module_qualified_name(file, indexing_root)
6163

62-
build_stub_tree_for_node(root, stub_root, module_fqn, false)
64+
if walker.current_node() != none {
65+
build_stub_tree_recurse(mut walker, file, stub_root, module_fqn, false)
66+
}
6367

6468
return &StubTree{
6569
root: stub_root
6670
}
6771
}
6872

69-
pub fn build_stub_tree_for_node(node psi.PsiElement, parent &psi.StubBase, module_fqn string, build_for_all_children bool) {
70-
element_type := psi.StubbedElementType{}
73+
fn build_stub_tree_recurse(mut tw psi.TreeWalker, file &psi.PsiFile, parent &psi.StubBase, module_fqn string, build_for_all_children bool) {
74+
node := tw.current_node() or { return }
75+
node_type := node.type_name
76+
77+
stub_type := psi.node_type_to_stub_type(node_type)
78+
is_stubbable := stub_type != .root || psi.node_is_type(node_type)
7179

72-
node_copy := node
80+
mut effective_parent := unsafe { parent }
81+
mut should_traverse_children := true
82+
mut pass_down_build_all := false
7383

74-
if node_copy is psi.StubBasedPsiElement || psi.node_is_type(node) || build_for_all_children {
75-
if stub := element_type.create_stub(node, parent, module_fqn) {
76-
is_qualified_type := node is psi.QualifiedType
77-
for child in node.children() {
78-
build_stub_tree_for_node(child, stub, module_fqn, build_for_all_children
79-
|| is_qualified_type)
84+
if is_stubbable || build_for_all_children {
85+
psi_element := psi.create_element(node, file)
86+
element_type := psi.StubbedElementType{}
87+
88+
if stub := element_type.create_stub(psi_element, parent, module_fqn) {
89+
effective_parent = unsafe { stub }
90+
if node_type == .qualified_type {
91+
pass_down_build_all = true
8092
}
8193
}
82-
return
8394
}
8495

85-
for child in node.children() {
86-
build_stub_tree_for_node(child, parent, module_fqn, false)
96+
if should_traverse_children {
97+
if tw.to_first_child() {
98+
for {
99+
build_stub_tree_recurse(mut tw, file, effective_parent, module_fqn,
100+
build_for_all_children || pass_down_build_all)
101+
102+
if !tw.next_sibling() {
103+
break
104+
}
105+
}
106+
tw.to_parent()
107+
}
87108
}
88109
}
89110

src/analyzer/parser/batch.v

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ pub fn parse_batch_files(files []string, count_workers int) []ParseResult {
3232
fn spawn_parser_workers(result_chan chan ParseResult, file_chan chan string, count_workers int) {
3333
mut wg := sync.new_waitgroup()
3434
wg.add(count_workers)
35+
3536
for i := 0; i < count_workers; i++ {
3637
spawn fn [file_chan, mut wg, result_chan] () {
38+
mut p := Parser.new()
39+
defer { p.free() }
3740
for {
3841
filepath := <-file_chan or { break }
39-
mut result := parse_file(filepath) or { continue }
42+
mut result := p.parse_file(filepath) or { continue }
4043
result.path = filepath
4144
result_chan <- result
4245
}
43-
4446
wg.done()
4547
}()
4648
}

src/analyzer/parser/parser.v

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ pub mut:
1515
// Source represent the possible types of V source code to parse.
1616
type Source = []byte | string
1717

18+
// Parser is a wrapper around the Tree-sitter V parser.
19+
pub struct Parser {
20+
mut:
21+
binding_parser &bindings.Parser[bindings.NodeType] = unsafe { nil }
22+
}
23+
24+
// new creates a new Parser instance.
25+
pub fn Parser.new() &Parser {
26+
mut bp := bindings.new_parser[bindings.NodeType](bindings.type_factory)
27+
bp.set_language(bindings.language)
28+
return &Parser{
29+
binding_parser: bp
30+
}
31+
}
32+
33+
// free frees the Tree-sitter parser.
34+
pub fn (p &Parser) free() {
35+
unsafe {
36+
p.binding_parser.free()
37+
}
38+
}
39+
1840
// parse_file parses a V source file and returns the corresponding `tree_sitter.Tree` and `Rope`.
1941
// If the file could not be read, an error is returned.
2042
// If the file was read successfully, but could not be parsed, the result
@@ -25,18 +47,19 @@ type Source = []byte | string
2547
// import parser
2648
//
2749
// fn main() {
28-
// res := parser.parse_file('foo.v') or {
50+
// mut p := parser.Parser.new()
51+
// res := p.parse_file('foo.v') or {
2952
// eprintln('Error: could not parse file: ${err}')
3053
// return
3154
// }
3255
// println(res.tree)
3356
// }
3457
// ```
35-
pub fn parse_file(filename string) !ParseResult {
36-
mut file := os.read_file(filename) or {
37-
return error('could not read file ${filename}: ${err}')
38-
}
39-
return parse_source(file)
58+
pub fn (mut p Parser) parse_file(filename string) !ParseResult {
59+
content := os.read_file(filename) or { return error('could not read file ${filename}: ${err}') }
60+
mut res := p.parse_source(content)
61+
res.path = filename
62+
return res
4063
}
4164

4265
// parse_source parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
@@ -48,14 +71,15 @@ pub fn parse_file(filename string) !ParseResult {
4871
// import parser
4972
//
5073
// fn main() {
51-
// res := parser.parse_source('fn main() { println("Hello, World!") }') or {
74+
// mut p := parser.Parser.new()
75+
// res := p.parse_source('fn main() { println("Hello, World!") }') or {
5276
// eprintln('Error: could not parse source: ${err}')
5377
// return
5478
// }
5579
// println(res.tree)
5680
// }
5781
// ```
58-
pub fn parse_source(source Source) ParseResult {
82+
pub fn (mut p Parser) parse_source(source Source) ParseResult {
5983
code := match source {
6084
string {
6185
source
@@ -64,14 +88,18 @@ pub fn parse_source(source Source) ParseResult {
6488
source.str()
6589
}
6690
}
67-
return parse_code(code)
91+
return p.parse_code(code)
6892
}
6993

7094
// parse_code parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
7195
// Unlike `parse_file` and `parse_source`, `parse_code` don't return an error since
7296
// the source is always valid.
73-
pub fn parse_code(code string) ParseResult {
74-
return parse_code_with_tree(code, unsafe { nil })
97+
pub fn (mut p Parser) parse_code(code string) ParseResult {
98+
tree := p.binding_parser.parse_string(source: code)
99+
return ParseResult{
100+
tree: tree
101+
source_text: code
102+
}
75103
}
76104

77105
// parse_code_with_tree parses a V code and returns the corresponding `tree_sitter.Tree` and `Rope`.
@@ -86,19 +114,18 @@ pub fn parse_code(code string) ParseResult {
86114
// import parser
87115
//
88116
// fn main() {
117+
// mut p := parser.Parser.new()
89118
// code := 'fn main() { println("Hello, World!") }'
90-
// res := parser.parse_code_with_tree(code, unsafe { nil })
119+
// res := p.parse_code_with_tree(code, unsafe { nil })
91120
// println(res.tree)
92121
// // some changes in code
93122
// code2 := 'fn foo() { println("Hello, World!") }'
94-
// res2 = parser.parse_code_with_tree(code2, res.tree)
123+
// res2 = p.parse_code_with_tree(code2, res.tree)
95124
// println(res2.tree
96125
// }
97-
pub fn parse_code_with_tree(code string, old_tree &bindings.Tree[bindings.NodeType]) ParseResult {
98-
mut parser := bindings.new_parser[bindings.NodeType](bindings.type_factory)
99-
parser.set_language(bindings.language)
126+
pub fn (mut p Parser) parse_code_with_tree(code string, old_tree &bindings.Tree[bindings.NodeType]) ParseResult {
100127
raw_tree := if isnil(old_tree) { unsafe { nil } } else { old_tree.raw_tree }
101-
tree := parser.parse_string(source: code, tree: raw_tree)
128+
tree := p.binding_parser.parse_string(source: code, tree: raw_tree)
102129
return ParseResult{
103130
tree: tree
104131
source_text: code

src/analyzer/psi/ConstantDefinition.v

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ pub fn (c &ConstantDefinition) is_public() bool {
1414

1515
pub fn (c &ConstantDefinition) get_type() types.Type {
1616
expr := c.expression() or { return types.unknown_type }
17-
return infer_type(expr)
17+
res := infer_type(expr)
18+
if c.stub_based() {
19+
if mut file := expr.containing_file() {
20+
file.free()
21+
}
22+
}
23+
return res
1824
}
1925

2026
fn (c &ConstantDefinition) identifier() ?PsiElement {
@@ -68,7 +74,9 @@ pub fn (c &ConstantDefinition) expression() ?PsiElement {
6874
if stub := c.get_stub() {
6975
file := c.containing_file() or { return none }
7076
// pretty hacky but it works
71-
res := parser.parse_code(stub.additional)
77+
mut p := parser.Parser.new()
78+
defer { p.free() }
79+
res := p.parse_code(stub.additional)
7280
root := res.tree.root_node()
7381
first_child := root.first_child()?
7482
next_first_child := first_child.first_child()?

src/analyzer/psi/EnumFieldDeclaration.v

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ pub fn (f &EnumFieldDeclaration) value() ?PsiElement {
6666
}
6767

6868
file := f.containing_file() or { return none }
69-
res := parser.parse_code(stub.additional)
69+
mut p := parser.Parser.new()
70+
defer { p.free() }
71+
res := p.parse_code(stub.additional)
7072
root := res.tree.root_node()
7173
first_child := root.first_child()?
7274
next_first_child := first_child.first_child()?
@@ -133,7 +135,13 @@ fn (f &EnumFieldDeclaration) get_value_impl() i64 {
133135

134136
if !is_flag {
135137
if value := f.value() {
136-
if val := f.calculate_value(value) {
138+
val := f.calculate_value(value)
139+
if f.stub_based() {
140+
if mut file := value.containing_file() {
141+
file.free()
142+
}
143+
}
144+
if val != none {
137145
return val
138146
}
139147
}

0 commit comments

Comments
 (0)