Skip to content

Commit daa41d2

Browse files
committed
Make LanguageService request-scoped with snapshot backing
1 parent 5d8dbf9 commit daa41d2

22 files changed

+485
-357
lines changed

internal/checker/checker_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ foo.bar;`
3939
}
4040
p := compiler.NewProgram(opts)
4141
p.BindSourceFiles()
42-
c, done := p.GetTypeChecker()
43-
defer done()
42+
c := p.GetTypeChecker()
4443
file := p.GetSourceFile("/foo.ts")
4544
interfaceId := file.Statements.Nodes[0].Name()
4645
varId := file.Statements.Nodes[1].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].Name()

internal/compiler/checkerpool.go

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package compiler
22

33
import (
4+
"context"
45
"sync"
56

67
"github.com/microsoft/typescript-go/internal/ast"
@@ -22,26 +23,28 @@ const (
2223
CheckerPoolModeDynamic
2324
)
2425

25-
type checkerPool struct {
26+
type CheckerPool struct {
2627
mode CheckerPoolMode
2728
maxCheckers int
2829
program *Program
2930

30-
mu sync.Mutex
31-
cond *sync.Cond
32-
createCheckersOnce sync.Once
33-
checkers []*checker.Checker
34-
inUse map[*checker.Checker]bool
35-
fileAssociations map[*ast.SourceFile]*checker.Checker
31+
mu sync.Mutex
32+
cond *sync.Cond
33+
createCheckersOnce sync.Once
34+
checkers []*checker.Checker
35+
inUse map[*checker.Checker]bool
36+
fileAssociations map[*ast.SourceFile]*checker.Checker
37+
requestAssociations map[string]*checker.Checker
3638
}
3739

38-
func newCheckerPool(mode CheckerPoolMode, maxCheckers int, program *Program) *checkerPool {
39-
pool := &checkerPool{
40-
mode: mode,
41-
program: program,
42-
maxCheckers: maxCheckers,
43-
checkers: make([]*checker.Checker, 0, maxCheckers),
44-
inUse: make(map[*checker.Checker]bool),
40+
func newCheckerPool(mode CheckerPoolMode, maxCheckers int, program *Program) *CheckerPool {
41+
pool := &CheckerPool{
42+
mode: mode,
43+
program: program,
44+
maxCheckers: maxCheckers,
45+
checkers: make([]*checker.Checker, 0, maxCheckers),
46+
inUse: make(map[*checker.Checker]bool),
47+
requestAssociations: make(map[string]*checker.Checker),
4548
}
4649

4750
if mode == CheckerPoolModeDynamic {
@@ -51,7 +54,7 @@ func newCheckerPool(mode CheckerPoolMode, maxCheckers int, program *Program) *ch
5154
return pool
5255
}
5356

54-
func (p *checkerPool) getCheckerForFile(file *ast.SourceFile) (*checker.Checker, func()) {
57+
func (p *CheckerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
5558
if p.mode == CheckerPoolModeStatic {
5659
p.createCheckers()
5760
checker := p.fileAssociations[file]
@@ -61,51 +64,74 @@ func (p *checkerPool) getCheckerForFile(file *ast.SourceFile) (*checker.Checker,
6164
p.mu.Lock()
6265
defer p.mu.Unlock()
6366

67+
requestID := core.GetRequestID(ctx)
68+
if requestID != "" {
69+
if checker, ok := p.requestAssociations[requestID]; ok {
70+
if inUse := p.inUse[checker]; !inUse {
71+
p.inUse[checker] = true
72+
return checker, p.createRelease(requestID, checker)
73+
}
74+
return checker, noop
75+
}
76+
}
77+
6478
if p.fileAssociations == nil {
6579
p.fileAssociations = make(map[*ast.SourceFile]*checker.Checker)
6680
}
6781

6882
if checker, ok := p.fileAssociations[file]; ok {
6983
if inUse := p.inUse[checker]; !inUse {
7084
p.inUse[checker] = true
71-
return checker, p.createRelease(checker)
85+
if requestID != "" {
86+
p.requestAssociations[requestID] = checker
87+
}
88+
return checker, p.createRelease(requestID, checker)
7289
}
7390
}
7491

75-
checker, release := p.getCheckerLocked()
92+
checker, release := p.getCheckerLocked(requestID)
7693
p.fileAssociations[file] = checker
7794
return checker, release
7895
}
7996

80-
func (p *checkerPool) getChecker() (*checker.Checker, func()) {
97+
func (p *CheckerPool) GetChecker(ctx context.Context) (*checker.Checker, func()) {
8198
if p.mode == CheckerPoolModeStatic {
8299
p.createCheckers()
83100
checker := p.checkers[0]
84101
return checker, noop
85102
}
86103
p.mu.Lock()
87104
defer p.mu.Unlock()
88-
return p.getCheckerLocked()
105+
return p.getCheckerLocked(core.GetRequestID(ctx))
89106
}
90107

91-
func (p *checkerPool) getCheckerLocked() (*checker.Checker, func()) {
108+
func (p *CheckerPool) getCheckerLocked(requestID string) (*checker.Checker, func()) {
92109
if checker := p.getImmediatelyAvailableChecker(); checker != nil {
93110
p.inUse[checker] = true
94-
return checker, p.createRelease(checker)
111+
if requestID != "" {
112+
p.requestAssociations[requestID] = checker
113+
}
114+
return checker, p.createRelease(requestID, checker)
95115
}
96116

97117
if len(p.checkers) < p.maxCheckers {
98118
checker := p.createCheckerLocked()
99119
p.inUse[checker] = true
100-
return checker, p.createRelease(checker)
120+
if requestID != "" {
121+
p.requestAssociations[requestID] = checker
122+
}
123+
return checker, p.createRelease(requestID, checker)
101124
}
102125

103126
checker := p.waitForAvailableChecker()
104127
p.inUse[checker] = true
105-
return checker, p.createRelease(checker)
128+
if requestID != "" {
129+
p.requestAssociations[requestID] = checker
130+
}
131+
return checker, p.createRelease(requestID, checker)
106132
}
107133

108-
func (p *checkerPool) getImmediatelyAvailableChecker() *checker.Checker {
134+
func (p *CheckerPool) getImmediatelyAvailableChecker() *checker.Checker {
109135
if len(p.checkers) == 0 {
110136
return nil
111137
}
@@ -119,7 +145,7 @@ func (p *checkerPool) getImmediatelyAvailableChecker() *checker.Checker {
119145
return nil
120146
}
121147

122-
func (p *checkerPool) waitForAvailableChecker() *checker.Checker {
148+
func (p *CheckerPool) waitForAvailableChecker() *checker.Checker {
123149
for {
124150
p.cond.Wait()
125151
checker := p.getImmediatelyAvailableChecker()
@@ -129,16 +155,17 @@ func (p *checkerPool) waitForAvailableChecker() *checker.Checker {
129155
}
130156
}
131157

132-
func (p *checkerPool) createRelease(checker *checker.Checker) func() {
158+
func (p *CheckerPool) createRelease(requestId string, checker *checker.Checker) func() {
133159
return func() {
134160
p.mu.Lock()
135161
defer p.mu.Unlock()
162+
136163
p.inUse[checker] = false
137164
p.cond.Signal()
138165
}
139166
}
140167

141-
func (p *checkerPool) createCheckers() {
168+
func (p *CheckerPool) createCheckers() {
142169
if p.mode != CheckerPoolModeStatic {
143170
panic("checkerPool.createCheckers() should only be called in static mode")
144171
}
@@ -164,13 +191,13 @@ func (p *checkerPool) createCheckers() {
164191
})
165192
}
166193

167-
func (p *checkerPool) createCheckerLocked() *checker.Checker {
194+
func (p *CheckerPool) createCheckerLocked() *checker.Checker {
168195
checker := checker.NewChecker(p.program)
169196
p.checkers = append(p.checkers, checker)
170197
return checker
171198
}
172199

173-
func (p *checkerPool) getAllCheckers() []*checker.Checker {
200+
func (p *CheckerPool) getAllCheckers() []*checker.Checker {
174201
if p.mode != CheckerPoolModeStatic {
175202
panic("checkerPool.getAllCheckers() should only be called in static mode")
176203
}

internal/compiler/emitHost.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ func (host *emitHost) WriteFile(fileName string, text string, writeByteOrderMark
3232
}
3333

3434
func (host *emitHost) GetEmitResolver(file *ast.SourceFile, skipDiagnostics bool) printer.EmitResolver {
35-
checker, done := host.program.GetTypeCheckerForFile(file)
36-
defer done()
35+
checker := host.program.GetTypeCheckerForFile(file)
3736
return checker.GetEmitResolver(file, skipDiagnostics)
3837
}
3938

internal/compiler/program.go

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type Program struct {
3636
compilerOptions *core.CompilerOptions
3737
configFileName string
3838
nodeModules map[string]*ast.SourceFile
39-
checkerPool *checkerPool
39+
checkerPool *CheckerPool
4040
currentDirectory string
4141
configFileParsingDiagnostics []*ast.Diagnostic
4242

@@ -219,9 +219,17 @@ func (p *Program) CheckSourceFiles() {
219219
wg.RunAndWait()
220220
}
221221

222+
func (p *Program) CheckerPool() *CheckerPool {
223+
return p.checkerPool
224+
}
225+
222226
// Return the type checker associated with the program.
223-
func (p *Program) GetTypeChecker() (*checker.Checker, func()) {
224-
return p.checkerPool.getChecker()
227+
func (p *Program) GetTypeChecker() *checker.Checker {
228+
if p.programOptions.CheckerPoolMode != CheckerPoolModeStatic {
229+
panic("program.GetTypeChecker() cannot be used with a dynamic checker pool. Use program.CheckerPool() instead.")
230+
}
231+
checker, _ := p.checkerPool.GetChecker(nil /*ctx*/)
232+
return checker
225233
}
226234

227235
func (p *Program) GetTypeCheckers() []*checker.Checker {
@@ -232,8 +240,12 @@ func (p *Program) GetTypeCheckers() []*checker.Checker {
232240
// method returns the checker that was tasked with checking the file. Note that it isn't possible to mix
233241
// types obtained from different checkers, so only non-type data (such as diagnostics or string
234242
// representations of types) should be obtained from checkers returned by this method.
235-
func (p *Program) GetTypeCheckerForFile(file *ast.SourceFile) (*checker.Checker, func()) {
236-
return p.checkerPool.getCheckerForFile(file)
243+
func (p *Program) GetTypeCheckerForFile(file *ast.SourceFile) *checker.Checker {
244+
if p.programOptions.CheckerPoolMode != CheckerPoolModeStatic {
245+
panic("program.GetTypeChecker() cannot be used with a dynamic checker pool. Use program.CheckerPool() instead.")
246+
}
247+
checker, _ := p.checkerPool.GetCheckerForFile(nil /*ctx*/, file)
248+
return checker
237249
}
238250

239251
func (p *Program) GetResolvedModule(file *ast.SourceFile, moduleReference string) *ast.SourceFile {
@@ -268,15 +280,13 @@ func (p *Program) GetSemanticDiagnostics(sourceFile *ast.SourceFile) []*ast.Diag
268280

269281
func (p *Program) GetGlobalDiagnostics() []*ast.Diagnostic {
270282
var globalDiagnostics []*ast.Diagnostic
271-
if p.programOptions.CheckerPoolMode == CheckerPoolModeStatic {
272-
checkers := p.checkerPool.getAllCheckers()
273-
for _, checker := range checkers {
274-
globalDiagnostics = append(globalDiagnostics, checker.GetGlobalDiagnostics()...)
275-
}
276-
} else {
277-
checker, done := p.GetTypeChecker()
278-
defer done()
279-
globalDiagnostics = checker.GetGlobalDiagnostics()
283+
if p.programOptions.CheckerPoolMode != CheckerPoolModeStatic {
284+
panic("program.GetGlobalDiagnostics() cannot be used with a dynamic checker pool. Use program.CheckerPool() instead.")
285+
}
286+
287+
checkers := p.checkerPool.getAllCheckers()
288+
for _, checker := range checkers {
289+
globalDiagnostics = append(globalDiagnostics, checker.GetGlobalDiagnostics()...)
280290
}
281291

282292
return SortAndDeduplicateDiagnostics(globalDiagnostics)
@@ -312,20 +322,19 @@ func (p *Program) getSemanticDiagnosticsForFile(sourceFile *ast.SourceFile) []*a
312322
return nil
313323
}
314324

325+
if p.programOptions.CheckerPoolMode != CheckerPoolModeStatic {
326+
panic("program.GetSemanticDiagnostics() cannot be used with a dynamic checker pool. Use program.CheckerPool() instead.")
327+
}
328+
315329
var fileChecker *checker.Checker
316330
var done func()
317331
if sourceFile != nil {
318-
fileChecker, done = p.checkerPool.getCheckerForFile(sourceFile)
332+
fileChecker, done = p.checkerPool.GetCheckerForFile(nil /*ctx*/, sourceFile)
319333
defer done()
320334
}
321335

322336
diags := slices.Clip(sourceFile.BindDiagnostics())
323-
var checkers []*checker.Checker
324-
if p.programOptions.CheckerPoolMode == CheckerPoolModeStatic {
325-
checkers = p.checkerPool.getAllCheckers()
326-
} else {
327-
checkers = []*checker.Checker{fileChecker}
328-
}
337+
checkers := p.checkerPool.getAllCheckers()
329338

330339
// Ask for diags from all checkers; checking one file may add diagnostics to other files.
331340
// These are deduplicated later.
@@ -489,8 +498,7 @@ func (p *Program) InstantiationCount() int {
489498
}
490499

491500
func (p *Program) PrintSourceFileWithTypes() {
492-
checker, done := p.GetTypeChecker()
493-
defer done()
501+
checker := p.GetTypeChecker()
494502
for _, file := range p.files {
495503
if tspath.GetBaseFileName(file.FileName()) == "main.ts" {
496504
fmt.Print(checker.SourceFileWithTypes(file))

internal/core/context.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package core
2+
3+
import "context"
4+
5+
type key int
6+
7+
var requestIDKey key
8+
9+
func WithRequestID(ctx context.Context, id string) context.Context {
10+
return context.WithValue(ctx, requestIDKey, id)
11+
}
12+
13+
func GetRequestID(ctx context.Context) string {
14+
if ctx == nil {
15+
return ""
16+
}
17+
if id, ok := ctx.Value(requestIDKey).(string); ok {
18+
return id
19+
}
20+
return ""
21+
}

internal/ls/api.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,17 @@ func (l *LanguageService) GetSymbolAtPosition(fileName string, position int) (*a
2323
if node == nil {
2424
return nil, fmt.Errorf("%w: %s:%d", ErrNoTokenAtPosition, fileName, position)
2525
}
26-
checker, done := program.GetTypeChecker()
27-
defer done()
26+
checker := program.GetTypeChecker()
2827
return checker.GetSymbolAtLocation(node), nil
2928
}
3029

3130
func (l *LanguageService) GetSymbolAtLocation(node *ast.Node) *ast.Symbol {
3231
program := l.GetProgram()
33-
checker, done := program.GetTypeChecker()
34-
defer done()
32+
checker := program.GetTypeChecker()
3533
return checker.GetSymbolAtLocation(node)
3634
}
3735

3836
func (l *LanguageService) GetTypeOfSymbol(symbol *ast.Symbol) *checker.Type {
39-
checker, done := l.GetProgram().GetTypeChecker()
40-
defer done()
37+
checker := l.GetProgram().GetTypeChecker()
4138
return checker.GetTypeOfSymbolAtLocation(symbol, nil)
4239
}

0 commit comments

Comments
 (0)