Skip to content

Commit 9cc4af7

Browse files
committed
internal/lsp: type check packages in parallel
This change eliminates the need for the importer struct. We should no longer need the "seen" map for cycle detection. This is because go/packages will not return import maps with cycles, and we fail in the Import function if we see an import we do not recognize. Change-Id: I06922c74e07eb47ce63b56fa2ac2099e7fc8bd8a Reviewed-on: https://go-review.googlesource.com/c/tools/+/202299 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Cottrell <[email protected]>
1 parent 9ba33e0 commit 9cc4af7

File tree

4 files changed

+53
-102
lines changed

4 files changed

+53
-102
lines changed

internal/lsp/cache/check.go

+50-95
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,6 @@ import (
2222
errors "golang.org/x/xerrors"
2323
)
2424

25-
type importer struct {
26-
snapshot *snapshot
27-
ctx context.Context
28-
29-
// seen maintains the set of previously imported packages.
30-
// If we have seen a package that is already in this map, we have a circular import.
31-
seen map[packageID]struct{}
32-
33-
// topLevelPackageID is the ID of the package from which type-checking began.
34-
topLevelPackageID packageID
35-
36-
// parentPkg is the package that imports the current package.
37-
parentPkg *pkg
38-
39-
// parentCheckPackageHandle is the check package handle that imports the current package.
40-
parentCheckPackageHandle *checkPackageHandle
41-
}
42-
4325
// checkPackageHandle implements source.CheckPackageHandle.
4426
type checkPackageHandle struct {
4527
handle *memoize.Handle
@@ -76,41 +58,47 @@ type checkPackageData struct {
7658
}
7759

7860
// checkPackageHandle returns a source.CheckPackageHandle for a given package and config.
79-
func (imp *importer) checkPackageHandle(ctx context.Context, id packageID) (*checkPackageHandle, error) {
80-
// Determine the mode that the files should be parsed in.
81-
mode := imp.mode(id)
82-
61+
func (s *snapshot) checkPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*checkPackageHandle, error) {
8362
// Check if we already have this CheckPackageHandle cached.
84-
if cph := imp.snapshot.getPackage(id, mode); cph != nil {
63+
if cph := s.getPackage(id, mode); cph != nil {
8564
return cph, nil
8665
}
8766

8867
// Build the CheckPackageHandle for this ID and its dependencies.
89-
cph, err := imp.buildKey(ctx, id, mode)
68+
cph, err := s.buildKey(ctx, id, mode)
9069
if err != nil {
9170
return nil, err
9271
}
93-
94-
h := imp.snapshot.view.session.cache.store.Bind(string(cph.key), func(ctx context.Context) interface{} {
72+
h := s.view.session.cache.store.Bind(string(cph.key), func(ctx context.Context) interface{} {
73+
// Begin loading the direct dependencies, in parallel.
74+
for _, impID := range cph.imports {
75+
dep := s.getPackage(impID, source.ParseExported)
76+
if dep == nil {
77+
continue
78+
}
79+
go func(dep *checkPackageHandle) {
80+
dep.check(ctx)
81+
}(dep)
82+
}
9583
data := &checkPackageData{}
96-
data.pkg, data.err = imp.typeCheck(ctx, cph)
84+
data.pkg, data.err = s.typeCheck(ctx, cph)
9785
return data
9886
})
9987
cph.handle = h
10088

10189
// Cache the CheckPackageHandle in the snapshot.
102-
imp.snapshot.addPackage(cph)
90+
s.addPackage(cph)
10391

10492
return cph, nil
10593
}
10694

10795
// buildKey computes the checkPackageKey for a given checkPackageHandle.
108-
func (imp *importer) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*checkPackageHandle, error) {
109-
m := imp.snapshot.getMetadata(id)
96+
func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*checkPackageHandle, error) {
97+
m := s.getMetadata(id)
11098
if m == nil {
11199
return nil, errors.Errorf("no metadata for %s", id)
112100
}
113-
phs, err := imp.parseGoHandles(ctx, m, mode)
101+
phs, err := s.parseGoHandles(ctx, m, mode)
114102
if err != nil {
115103
return nil, err
116104
}
@@ -127,17 +115,12 @@ func (imp *importer) buildKey(ctx context.Context, id packageID, mode source.Par
127115
return deps[i] < deps[j]
128116
})
129117

130-
// Create the dep importer for use on the dependency handles.
131-
depImporter := &importer{
132-
snapshot: imp.snapshot,
133-
topLevelPackageID: imp.topLevelPackageID,
134-
}
135118
// Begin computing the key by getting the depKeys for all dependencies.
136119
var depKeys [][]byte
137-
for _, dep := range deps {
138-
depHandle, err := depImporter.checkPackageHandle(ctx, dep)
120+
for _, depID := range deps {
121+
depHandle, err := s.checkPackageHandle(ctx, depID, source.ParseExported)
139122
if err != nil {
140-
log.Error(ctx, "no dep handle", err, telemetry.Package.Of(dep))
123+
log.Error(ctx, "no dep handle", err, telemetry.Package.Of(depID))
141124

142125
// One bad dependency should not prevent us from checking the entire package.
143126
// Add a special key to mark a bad dependency.
@@ -215,52 +198,20 @@ func (cph *checkPackageHandle) cached() (*pkg, error) {
215198
return data.pkg, data.err
216199
}
217200

218-
func (imp *importer) parseGoHandles(ctx context.Context, m *metadata, mode source.ParseMode) ([]source.ParseGoHandle, error) {
201+
func (s *snapshot) parseGoHandles(ctx context.Context, m *metadata, mode source.ParseMode) ([]source.ParseGoHandle, error) {
219202
phs := make([]source.ParseGoHandle, 0, len(m.files))
220203
for _, uri := range m.files {
221-
f, err := imp.snapshot.view.GetFile(ctx, uri)
204+
f, err := s.view.GetFile(ctx, uri)
222205
if err != nil {
223206
return nil, err
224207
}
225-
fh := imp.snapshot.Handle(ctx, f)
226-
phs = append(phs, imp.snapshot.view.session.cache.ParseGoHandle(fh, mode))
208+
fh := s.Handle(ctx, f)
209+
phs = append(phs, s.view.session.cache.ParseGoHandle(fh, mode))
227210
}
228211
return phs, nil
229212
}
230213

231-
func (imp *importer) mode(id packageID) source.ParseMode {
232-
if imp.topLevelPackageID == id {
233-
return source.ParseFull
234-
}
235-
return source.ParseExported
236-
}
237-
238-
func (imp *importer) Import(pkgPath string) (*types.Package, error) {
239-
ctx, done := trace.StartSpan(imp.ctx, "cache.importer.Import", telemetry.PackagePath.Of(pkgPath))
240-
defer done()
241-
242-
// We need to set the parent package's imports, so there should always be one.
243-
if imp.parentPkg == nil {
244-
return nil, errors.Errorf("no parent package for import %s", pkgPath)
245-
}
246-
// Get the CheckPackageHandle from the importing package.
247-
id, ok := imp.parentCheckPackageHandle.imports[packagePath(pkgPath)]
248-
if !ok {
249-
return nil, errors.Errorf("no package data for import path %s", pkgPath)
250-
}
251-
cph := imp.snapshot.getPackage(id, source.ParseExported)
252-
if cph == nil {
253-
return nil, errors.Errorf("no cached package for %s", id)
254-
}
255-
pkg, err := cph.check(ctx)
256-
if err != nil {
257-
return nil, err
258-
}
259-
imp.parentPkg.imports[packagePath(pkgPath)] = pkg
260-
return pkg.GetTypes(), nil
261-
}
262-
263-
func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*pkg, error) {
214+
func (s *snapshot) typeCheck(ctx context.Context, cph *checkPackageHandle) (*pkg, error) {
264215
ctx, done := trace.StartSpan(ctx, "cache.importer.typeCheck", telemetry.Package.Of(cph.m.id))
265216
defer done()
266217

@@ -270,7 +221,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*p
270221
}
271222

272223
pkg := &pkg{
273-
view: imp.snapshot.view,
224+
view: s.view,
274225
id: cph.m.id,
275226
mode: cph.mode,
276227
pkgPath: cph.m.pkgPath,
@@ -329,9 +280,24 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*p
329280
Error: func(e error) {
330281
rawErrors = append(rawErrors, e)
331282
},
332-
Importer: imp.depImporter(ctx, cph, pkg),
283+
Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
284+
impID, ok := cph.imports[packagePath(pkgPath)]
285+
if !ok {
286+
return nil, errors.Errorf("no package data for import %s", pkgPath)
287+
}
288+
dep := s.getPackage(impID, source.ParseExported)
289+
if dep == nil {
290+
return nil, errors.Errorf("no package for import %s", impID)
291+
}
292+
depPkg, err := dep.check(ctx)
293+
if err != nil {
294+
return nil, err
295+
}
296+
pkg.imports[depPkg.pkgPath] = depPkg
297+
return depPkg.types, nil
298+
}),
333299
}
334-
check := types.NewChecker(cfg, imp.snapshot.view.session.cache.FileSet(), pkg.types, pkg.typesInfo)
300+
check := types.NewChecker(cfg, s.view.session.cache.FileSet(), pkg.types, pkg.typesInfo)
335301

336302
// Type checking errors are handled via the config, so ignore them here.
337303
_ = check.Files(files)
@@ -350,19 +316,8 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*p
350316
return pkg, nil
351317
}
352318

353-
func (imp *importer) depImporter(ctx context.Context, cph *checkPackageHandle, pkg *pkg) *importer {
354-
// Handle circular imports by copying previously seen imports.
355-
seen := make(map[packageID]struct{})
356-
for k, v := range imp.seen {
357-
seen[k] = v
358-
}
359-
seen[pkg.id] = struct{}{}
360-
return &importer{
361-
snapshot: imp.snapshot,
362-
topLevelPackageID: imp.topLevelPackageID,
363-
parentPkg: pkg,
364-
parentCheckPackageHandle: cph,
365-
seen: seen,
366-
ctx: ctx,
367-
}
368-
}
319+
// An importFunc is an implementation of the single-method
320+
// types.Importer interface based on a function value.
321+
type importerFunc func(path string) (*types.Package, error)
322+
323+
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }

internal/lsp/cache/gofile.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,7 @@ func (s *snapshot) CheckPackageHandles(ctx context.Context, f source.File) ([]so
5252
if check {
5353
var results []source.CheckPackageHandle
5454
for _, m := range m {
55-
imp := &importer{
56-
snapshot: s,
57-
topLevelPackageID: m.id,
58-
seen: make(map[packageID]struct{}),
59-
}
60-
cph, err := imp.checkPackageHandle(ctx, m.id)
55+
cph, err := s.checkPackageHandle(ctx, m.id, source.ParseFull)
6156
if err != nil {
6257
return nil, err
6358
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package bad
22

33
import (
4-
_ "nosuchpkg" //@diag("_", "compiler", "could not import nosuchpkg (no package data for import path nosuchpkg)")
4+
_ "nosuchpkg" //@diag("_", "compiler", "could not import nosuchpkg (no package data for import nosuchpkg)")
55
)

internal/lsp/watched_files.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2019 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
4+
45
package lsp
56

67
import (

0 commit comments

Comments
 (0)