Skip to content

Commit 9901302

Browse files
stamblerrebradfitz
authored andcommitted
internal/lsp: use dependencies in cache keys
This change includes dependencies in the cache keys for CheckPackageHandles. This should fix the issue with propagating results to reverse dependencies. Updates golang/go#34410 Change-Id: I025b7f0d6b0dcaa89c3461ebd94eadd35de4955f Reviewed-on: https://go-review.googlesource.com/c/tools/+/198317 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Cottrell <[email protected]>
1 parent f7bb6f1 commit 9901302

File tree

9 files changed

+217
-177
lines changed

9 files changed

+217
-177
lines changed

internal/lsp/cache/check.go

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@ package cache
77
import (
88
"bytes"
99
"context"
10+
"fmt"
1011
"go/ast"
1112
"go/scanner"
1213
"go/types"
14+
"sort"
1315
"sync"
1416

1517
"golang.org/x/tools/go/analysis"
1618
"golang.org/x/tools/go/packages"
1719
"golang.org/x/tools/internal/lsp/source"
1820
"golang.org/x/tools/internal/lsp/telemetry"
1921
"golang.org/x/tools/internal/memoize"
20-
"golang.org/x/tools/internal/telemetry/log"
2122
"golang.org/x/tools/internal/telemetry/trace"
2223
errors "golang.org/x/xerrors"
2324
)
2425

2526
type importer struct {
2627
snapshot *snapshot
2728
ctx context.Context
28-
config *packages.Config
2929

3030
// seen maintains the set of previously imported packages.
3131
// If we have seen a package that is already in this map, we have a circular import.
@@ -41,38 +41,31 @@ type importer struct {
4141
parentCheckPackageHandle *checkPackageHandle
4242
}
4343

44-
// checkPackageKey uniquely identifies a package and its config.
45-
type checkPackageKey struct {
46-
id string
47-
files string
48-
config string
49-
50-
// TODO: For now, we don't include dependencies in the key.
51-
// This will be necessary when we change the cache invalidation logic.
52-
}
53-
5444
// checkPackageHandle implements source.CheckPackageHandle.
5545
type checkPackageHandle struct {
5646
handle *memoize.Handle
5747

58-
files []source.ParseGoHandle
59-
imports map[packagePath]*checkPackageHandle
48+
// files are the ParseGoHandles that compose the package.
49+
files []source.ParseGoHandle
50+
51+
// mode is the mode the the files were parsed in.
52+
mode source.ParseMode
6053

61-
m *metadata
62-
config *packages.Config
54+
// imports is the map of the package's imports.
55+
imports map[packagePath]packageID
56+
57+
// m is the metadata associated with the package.
58+
m *metadata
59+
60+
// key is the hashed key for the package.
61+
key []byte
6362
}
6463

65-
func (cph *checkPackageHandle) Mode() source.ParseMode {
66-
if len(cph.files) == 0 {
67-
return -1
68-
}
69-
mode := cph.files[0].Mode()
70-
for _, ph := range cph.files[1:] {
71-
if ph.Mode() != mode {
72-
return -1
73-
}
64+
func (cph *checkPackageHandle) packageKey() packageKey {
65+
return packageKey{
66+
id: cph.m.id,
67+
mode: cph.mode,
7468
}
75-
return mode
7669
}
7770

7871
// checkPackageData contains the data produced by type-checking a package.
@@ -84,38 +77,82 @@ type checkPackageData struct {
8477
}
8578

8679
// checkPackageHandle returns a source.CheckPackageHandle for a given package and config.
87-
func (imp *importer) checkPackageHandle(ctx context.Context, id packageID, s *snapshot) (*checkPackageHandle, error) {
88-
m := s.getMetadata(id)
80+
func (imp *importer) checkPackageHandle(ctx context.Context, id packageID) (*checkPackageHandle, error) {
81+
// Determine the mode that the files should be parsed in.
82+
mode := imp.mode(id)
83+
84+
// Check if we already have this CheckPackageHandle cached.
85+
if cph := imp.snapshot.getPackage(id, mode); cph != nil {
86+
return cph, nil
87+
}
88+
89+
// Build the CheckPackageHandle for this ID and its dependencies.
90+
cph, err := imp.buildKey(ctx, id, mode)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
h := imp.snapshot.view.session.cache.store.Bind(string(cph.key), func(ctx context.Context) interface{} {
96+
data := &checkPackageData{}
97+
data.pkg, data.err = imp.typeCheck(ctx, cph)
98+
return data
99+
})
100+
cph.handle = h
101+
102+
return cph, nil
103+
}
104+
105+
// buildKey computes the checkPackageKey for a given checkPackageHandle.
106+
func (imp *importer) buildKey(ctx context.Context, id packageID, mode source.ParseMode) (*checkPackageHandle, error) {
107+
m := imp.snapshot.getMetadata(id)
89108
if m == nil {
90109
return nil, errors.Errorf("no metadata for %s", id)
91110
}
92-
phs, err := imp.parseGoHandles(ctx, m)
111+
112+
phs, err := imp.parseGoHandles(ctx, m, mode)
93113
if err != nil {
94-
log.Error(ctx, "no ParseGoHandles", err, telemetry.Package.Of(id))
95114
return nil, err
96115
}
97116
cph := &checkPackageHandle{
98117
m: m,
99118
files: phs,
100-
config: imp.config,
101-
imports: make(map[packagePath]*checkPackageHandle),
119+
imports: make(map[packagePath]packageID),
120+
mode: mode,
102121
}
103-
h := imp.snapshot.view.session.cache.store.Bind(cph.key(), func(ctx context.Context) interface{} {
104-
data := &checkPackageData{}
105-
data.pkg, data.err = imp.typeCheck(ctx, cph, m)
106-
return data
122+
123+
// Make sure all of the deps are sorted.
124+
deps := append([]packageID{}, m.deps...)
125+
sort.Slice(deps, func(i, j int) bool {
126+
return deps[i] < deps[j]
107127
})
108128

109-
cph.handle = h
129+
// Create the dep importer for use on the dependency handles.
130+
depImporter := &importer{
131+
snapshot: imp.snapshot,
132+
topLevelPackageID: imp.topLevelPackageID,
133+
}
134+
// Begin computing the key by getting the depKeys for all dependencies.
135+
var depKeys [][]byte
136+
for _, dep := range deps {
137+
depHandle, err := depImporter.checkPackageHandle(ctx, dep)
138+
if err != nil {
139+
return nil, errors.Errorf("no dep handle for %s: %+v", dep, err)
140+
}
141+
cph.imports[depHandle.m.pkgPath] = depHandle.m.id
142+
depKeys = append(depKeys, depHandle.key)
143+
}
144+
cph.key = checkPackageKey(cph.m.id, cph.files, m.config, depKeys)
110145

111146
// Cache the CheckPackageHandle in the snapshot.
112-
for _, ph := range cph.files {
113-
uri := ph.File().Identity().URI
114-
s.addPackage(uri, cph)
115-
}
147+
imp.snapshot.addPackage(cph)
148+
116149
return cph, nil
117150
}
118151

152+
func checkPackageKey(id packageID, phs []source.ParseGoHandle, cfg *packages.Config, deps [][]byte) []byte {
153+
return []byte(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(phs), hashConfig(cfg), hashContents(bytes.Join(deps, nil))))))
154+
}
155+
119156
// hashConfig returns the hash for the *packages.Config.
120157
func hashConfig(config *packages.Config) string {
121158
b := bytes.NewBuffer(nil)
@@ -143,16 +180,12 @@ func (cph *checkPackageHandle) check(ctx context.Context) (*pkg, error) {
143180

144181
v := cph.handle.Get(ctx)
145182
if v == nil {
146-
return nil, ctx.Err()
183+
return nil, errors.Errorf("no package for %s", cph.m.id)
147184
}
148185
data := v.(*checkPackageData)
149186
return data.pkg, data.err
150187
}
151188

152-
func (cph *checkPackageHandle) Config() *packages.Config {
153-
return cph.config
154-
}
155-
156189
func (cph *checkPackageHandle) Files() []source.ParseGoHandle {
157190
return cph.files
158191
}
@@ -182,31 +215,26 @@ func (cph *checkPackageHandle) cached(ctx context.Context) (*pkg, error) {
182215
return data.pkg, data.err
183216
}
184217

185-
func (cph *checkPackageHandle) key() checkPackageKey {
186-
return checkPackageKey{
187-
id: string(cph.m.id),
188-
files: hashParseKeys(cph.files),
189-
config: hashConfig(cph.config),
190-
}
191-
}
192-
193-
func (imp *importer) parseGoHandles(ctx context.Context, m *metadata) ([]source.ParseGoHandle, error) {
218+
func (imp *importer) parseGoHandles(ctx context.Context, m *metadata, mode source.ParseMode) ([]source.ParseGoHandle, error) {
194219
phs := make([]source.ParseGoHandle, 0, len(m.files))
195220
for _, uri := range m.files {
196221
f, err := imp.snapshot.view.GetFile(ctx, uri)
197222
if err != nil {
198223
return nil, err
199224
}
200225
fh := imp.snapshot.Handle(ctx, f)
201-
mode := source.ParseExported
202-
if imp.topLevelPackageID == m.id {
203-
mode = source.ParseFull
204-
}
205226
phs = append(phs, imp.snapshot.view.session.cache.ParseGoHandle(fh, mode))
206227
}
207228
return phs, nil
208229
}
209230

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+
210238
func (imp *importer) Import(pkgPath string) (*types.Package, error) {
211239
ctx, done := trace.StartSpan(imp.ctx, "cache.importer.Import", telemetry.PackagePath.Of(pkgPath))
212240
defer done()
@@ -215,16 +243,14 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
215243
if imp.parentPkg == nil {
216244
return nil, errors.Errorf("no parent package for import %s", pkgPath)
217245
}
218-
219246
// Get the CheckPackageHandle from the importing package.
220-
cph, ok := imp.parentCheckPackageHandle.imports[packagePath(pkgPath)]
247+
id, ok := imp.parentCheckPackageHandle.imports[packagePath(pkgPath)]
221248
if !ok {
222249
return nil, errors.Errorf("no package data for import path %s", pkgPath)
223250
}
224-
for _, ph := range cph.Files() {
225-
if ph.Mode() != source.ParseExported {
226-
panic("dependency parsed in full mode")
227-
}
251+
cph := imp.snapshot.getPackage(id, source.ParseExported)
252+
if cph == nil {
253+
return nil, errors.Errorf("no package for %s", id)
228254
}
229255
pkg, err := cph.check(ctx)
230256
if err != nil {
@@ -234,17 +260,17 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
234260
return pkg.GetTypes(), nil
235261
}
236262

237-
func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *metadata) (*pkg, error) {
238-
ctx, done := trace.StartSpan(ctx, "cache.importer.typeCheck", telemetry.Package.Of(m.id))
263+
func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle) (*pkg, error) {
264+
ctx, done := trace.StartSpan(ctx, "cache.importer.typeCheck", telemetry.Package.Of(cph.m.id))
239265
defer done()
240266

241267
pkg := &pkg{
242268
view: imp.snapshot.view,
243-
id: m.id,
244-
pkgPath: m.pkgPath,
269+
id: cph.m.id,
270+
pkgPath: cph.m.pkgPath,
245271
files: cph.Files(),
246272
imports: make(map[packagePath]*pkg),
247-
typesSizes: m.typesSizes,
273+
typesSizes: cph.m.typesSizes,
248274
typesInfo: &types.Info{
249275
Types: make(map[ast.Expr]types.TypeAndValue),
250276
Defs: make(map[*ast.Ident]types.Object),
@@ -257,22 +283,8 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
257283
}
258284
// If the package comes back with errors from `go list`,
259285
// don't bother type-checking it.
260-
for _, err := range m.errors {
261-
pkg.errors = append(m.errors, err)
262-
}
263-
// Set imports of package to correspond to cached packages.
264-
cimp := imp.child(ctx, pkg, cph)
265-
for _, depID := range m.deps {
266-
dep := imp.snapshot.getMetadata(depID)
267-
if dep == nil {
268-
continue
269-
}
270-
depHandle, err := cimp.checkPackageHandle(ctx, depID, imp.snapshot)
271-
if err != nil {
272-
log.Error(ctx, "no check package handle", err, telemetry.Package.Of(depID))
273-
continue
274-
}
275-
cph.imports[dep.pkgPath] = depHandle
286+
for _, err := range cph.m.errors {
287+
pkg.errors = append(cph.m.errors, err)
276288
}
277289
var (
278290
files = make([]*ast.File, len(pkg.files))
@@ -305,19 +317,19 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
305317
files = files[:i]
306318

307319
// Use the default type information for the unsafe package.
308-
if m.pkgPath == "unsafe" {
320+
if cph.m.pkgPath == "unsafe" {
309321
pkg.types = types.Unsafe
310322
} else if len(files) == 0 { // not the unsafe package, no parsed files
311323
return nil, errors.Errorf("no parsed files for package %s", pkg.pkgPath)
312324
} else {
313-
pkg.types = types.NewPackage(string(m.pkgPath), m.name)
325+
pkg.types = types.NewPackage(string(cph.m.pkgPath), cph.m.name)
314326
}
315327

316328
cfg := &types.Config{
317329
Error: func(err error) {
318330
imp.snapshot.view.session.cache.appendPkgError(pkg, err)
319331
},
320-
Importer: cimp,
332+
Importer: imp.depImporter(ctx, cph, pkg),
321333
}
322334
check := types.NewChecker(cfg, imp.snapshot.view.session.cache.FileSet(), pkg.types, pkg.typesInfo)
323335

@@ -327,7 +339,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
327339
return pkg, nil
328340
}
329341

330-
func (imp *importer) child(ctx context.Context, pkg *pkg, cph *checkPackageHandle) *importer {
342+
func (imp *importer) depImporter(ctx context.Context, cph *checkPackageHandle, pkg *pkg) *importer {
331343
// Handle circular imports by copying previously seen imports.
332344
seen := make(map[packageID]struct{})
333345
for k, v := range imp.seen {
@@ -336,12 +348,11 @@ func (imp *importer) child(ctx context.Context, pkg *pkg, cph *checkPackageHandl
336348
seen[pkg.id] = struct{}{}
337349
return &importer{
338350
snapshot: imp.snapshot,
339-
ctx: ctx,
340-
config: imp.config,
341-
seen: seen,
342351
topLevelPackageID: imp.topLevelPackageID,
343352
parentPkg: pkg,
344353
parentCheckPackageHandle: cph,
354+
seen: seen,
355+
ctx: ctx,
345356
}
346357
}
347358

0 commit comments

Comments
 (0)