@@ -7,25 +7,25 @@ package cache
7
7
import (
8
8
"bytes"
9
9
"context"
10
+ "fmt"
10
11
"go/ast"
11
12
"go/scanner"
12
13
"go/types"
14
+ "sort"
13
15
"sync"
14
16
15
17
"golang.org/x/tools/go/analysis"
16
18
"golang.org/x/tools/go/packages"
17
19
"golang.org/x/tools/internal/lsp/source"
18
20
"golang.org/x/tools/internal/lsp/telemetry"
19
21
"golang.org/x/tools/internal/memoize"
20
- "golang.org/x/tools/internal/telemetry/log"
21
22
"golang.org/x/tools/internal/telemetry/trace"
22
23
errors "golang.org/x/xerrors"
23
24
)
24
25
25
26
type importer struct {
26
27
snapshot * snapshot
27
28
ctx context.Context
28
- config * packages.Config
29
29
30
30
// seen maintains the set of previously imported packages.
31
31
// If we have seen a package that is already in this map, we have a circular import.
@@ -41,38 +41,31 @@ type importer struct {
41
41
parentCheckPackageHandle * checkPackageHandle
42
42
}
43
43
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
-
54
44
// checkPackageHandle implements source.CheckPackageHandle.
55
45
type checkPackageHandle struct {
56
46
handle * memoize.Handle
57
47
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
60
53
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
63
62
}
64
63
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 ,
74
68
}
75
- return mode
76
69
}
77
70
78
71
// checkPackageData contains the data produced by type-checking a package.
@@ -84,38 +77,82 @@ type checkPackageData struct {
84
77
}
85
78
86
79
// 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 )
89
108
if m == nil {
90
109
return nil , errors .Errorf ("no metadata for %s" , id )
91
110
}
92
- phs , err := imp .parseGoHandles (ctx , m )
111
+
112
+ phs , err := imp .parseGoHandles (ctx , m , mode )
93
113
if err != nil {
94
- log .Error (ctx , "no ParseGoHandles" , err , telemetry .Package .Of (id ))
95
114
return nil , err
96
115
}
97
116
cph := & checkPackageHandle {
98
117
m : m ,
99
118
files : phs ,
100
- config : imp . config ,
101
- imports : make ( map [ packagePath ] * checkPackageHandle ) ,
119
+ imports : make ( map [ packagePath ] packageID ) ,
120
+ mode : mode ,
102
121
}
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 ]
107
127
})
108
128
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 )
110
145
111
146
// 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
+
116
149
return cph , nil
117
150
}
118
151
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
+
119
156
// hashConfig returns the hash for the *packages.Config.
120
157
func hashConfig (config * packages.Config ) string {
121
158
b := bytes .NewBuffer (nil )
@@ -143,16 +180,12 @@ func (cph *checkPackageHandle) check(ctx context.Context) (*pkg, error) {
143
180
144
181
v := cph .handle .Get (ctx )
145
182
if v == nil {
146
- return nil , ctx . Err ( )
183
+ return nil , errors . Errorf ( "no package for %s" , cph . m . id )
147
184
}
148
185
data := v .(* checkPackageData )
149
186
return data .pkg , data .err
150
187
}
151
188
152
- func (cph * checkPackageHandle ) Config () * packages.Config {
153
- return cph .config
154
- }
155
-
156
189
func (cph * checkPackageHandle ) Files () []source.ParseGoHandle {
157
190
return cph .files
158
191
}
@@ -182,31 +215,26 @@ func (cph *checkPackageHandle) cached(ctx context.Context) (*pkg, error) {
182
215
return data .pkg , data .err
183
216
}
184
217
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 ) {
194
219
phs := make ([]source.ParseGoHandle , 0 , len (m .files ))
195
220
for _ , uri := range m .files {
196
221
f , err := imp .snapshot .view .GetFile (ctx , uri )
197
222
if err != nil {
198
223
return nil , err
199
224
}
200
225
fh := imp .snapshot .Handle (ctx , f )
201
- mode := source .ParseExported
202
- if imp .topLevelPackageID == m .id {
203
- mode = source .ParseFull
204
- }
205
226
phs = append (phs , imp .snapshot .view .session .cache .ParseGoHandle (fh , mode ))
206
227
}
207
228
return phs , nil
208
229
}
209
230
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
+
210
238
func (imp * importer ) Import (pkgPath string ) (* types.Package , error ) {
211
239
ctx , done := trace .StartSpan (imp .ctx , "cache.importer.Import" , telemetry .PackagePath .Of (pkgPath ))
212
240
defer done ()
@@ -215,16 +243,14 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
215
243
if imp .parentPkg == nil {
216
244
return nil , errors .Errorf ("no parent package for import %s" , pkgPath )
217
245
}
218
-
219
246
// Get the CheckPackageHandle from the importing package.
220
- cph , ok := imp .parentCheckPackageHandle .imports [packagePath (pkgPath )]
247
+ id , ok := imp .parentCheckPackageHandle .imports [packagePath (pkgPath )]
221
248
if ! ok {
222
249
return nil , errors .Errorf ("no package data for import path %s" , pkgPath )
223
250
}
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 )
228
254
}
229
255
pkg , err := cph .check (ctx )
230
256
if err != nil {
@@ -234,17 +260,17 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
234
260
return pkg .GetTypes (), nil
235
261
}
236
262
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 ))
239
265
defer done ()
240
266
241
267
pkg := & pkg {
242
268
view : imp .snapshot .view ,
243
- id : m .id ,
244
- pkgPath : m .pkgPath ,
269
+ id : cph . m .id ,
270
+ pkgPath : cph . m .pkgPath ,
245
271
files : cph .Files (),
246
272
imports : make (map [packagePath ]* pkg ),
247
- typesSizes : m .typesSizes ,
273
+ typesSizes : cph . m .typesSizes ,
248
274
typesInfo : & types.Info {
249
275
Types : make (map [ast.Expr ]types.TypeAndValue ),
250
276
Defs : make (map [* ast.Ident ]types.Object ),
@@ -257,22 +283,8 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
257
283
}
258
284
// If the package comes back with errors from `go list`,
259
285
// 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 )
276
288
}
277
289
var (
278
290
files = make ([]* ast.File , len (pkg .files ))
@@ -305,19 +317,19 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
305
317
files = files [:i ]
306
318
307
319
// Use the default type information for the unsafe package.
308
- if m .pkgPath == "unsafe" {
320
+ if cph . m .pkgPath == "unsafe" {
309
321
pkg .types = types .Unsafe
310
322
} else if len (files ) == 0 { // not the unsafe package, no parsed files
311
323
return nil , errors .Errorf ("no parsed files for package %s" , pkg .pkgPath )
312
324
} else {
313
- pkg .types = types .NewPackage (string (m .pkgPath ), m .name )
325
+ pkg .types = types .NewPackage (string (cph . m .pkgPath ), cph . m .name )
314
326
}
315
327
316
328
cfg := & types.Config {
317
329
Error : func (err error ) {
318
330
imp .snapshot .view .session .cache .appendPkgError (pkg , err )
319
331
},
320
- Importer : cimp ,
332
+ Importer : imp . depImporter ( ctx , cph , pkg ) ,
321
333
}
322
334
check := types .NewChecker (cfg , imp .snapshot .view .session .cache .FileSet (), pkg .types , pkg .typesInfo )
323
335
@@ -327,7 +339,7 @@ func (imp *importer) typeCheck(ctx context.Context, cph *checkPackageHandle, m *
327
339
return pkg , nil
328
340
}
329
341
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 {
331
343
// Handle circular imports by copying previously seen imports.
332
344
seen := make (map [packageID ]struct {})
333
345
for k , v := range imp .seen {
@@ -336,12 +348,11 @@ func (imp *importer) child(ctx context.Context, pkg *pkg, cph *checkPackageHandl
336
348
seen [pkg .id ] = struct {}{}
337
349
return & importer {
338
350
snapshot : imp .snapshot ,
339
- ctx : ctx ,
340
- config : imp .config ,
341
- seen : seen ,
342
351
topLevelPackageID : imp .topLevelPackageID ,
343
352
parentPkg : pkg ,
344
353
parentCheckPackageHandle : cph ,
354
+ seen : seen ,
355
+ ctx : ctx ,
345
356
}
346
357
}
347
358
0 commit comments