@@ -11,7 +11,6 @@ import (
11
11
"go/ast"
12
12
"go/token"
13
13
"go/types"
14
- "log"
15
14
"regexp"
16
15
"runtime"
17
16
"sort"
@@ -64,8 +63,8 @@ type typeCheckBatch struct {
64
63
// In fact, parsedFiles may be counter-productive due to pinning all files in
65
64
// memory during large operations.
66
65
parsedFiles map [span.URI ]* source.ParsedGoFile // parsed files necessary for type-checking
67
- imports map [PackageID ]pkgOrErr // types.Packages to use for importing
68
66
packages map [PackageID ]* Package
67
+ errors map [PackageID ]error // lazily allocated
69
68
}
70
69
71
70
type pkgOrErr struct {
@@ -85,14 +84,12 @@ type pkgOrErr struct {
85
84
func (s * snapshot ) TypeCheck (ctx context.Context , ids ... PackageID ) ([]source.Package , error ) {
86
85
// Shared state for efficient type-checking.
87
86
b := & typeCheckBatch {
88
- fset : fileSetWithBase (reservedForParsing ),
89
- parseCache : s .parseCache ,
90
- cpulimit : make (chan struct {}, runtime .GOMAXPROCS (0 )),
91
- needSyntax : make (map [PackageID ]bool ),
92
-
93
- parsedFiles : make (map [span.URI ]* source.ParsedGoFile ),
87
+ fset : fileSetWithBase (reservedForParsing ),
88
+ parseCache : s .parseCache ,
89
+ cpulimit : make (chan struct {}, runtime .GOMAXPROCS (0 )),
90
+ needSyntax : make (map [PackageID ]bool ),
94
91
promises : make (map [PackageID ]* memoize.Promise ),
95
- imports : make (map [PackageID ] pkgOrErr ),
92
+ parsedFiles : make (map [span. URI ] * source. ParsedGoFile ),
96
93
packages : make (map [PackageID ]* Package ),
97
94
}
98
95
@@ -155,11 +152,15 @@ func (s *snapshot) TypeCheck(ctx context.Context, ids ...PackageID) ([]source.Pa
155
152
debugName := fmt .Sprintf ("check(%s)" , id )
156
153
b .promises [id ] = memoize .NewPromise (debugName , func (ctx context.Context , _ interface {}) interface {} {
157
154
pkg , err := b .processPackage (ctx , ph )
158
-
159
- b .mu .Lock ()
160
- b .imports [m .ID ] = pkgOrErr {pkg , err }
161
- b .mu .Unlock ()
162
- return nil
155
+ if err != nil {
156
+ b .mu .Lock ()
157
+ if b .errors == nil {
158
+ b .errors = make (map [PackageID ]error )
159
+ }
160
+ b .errors [id ] = err
161
+ b .mu .Unlock ()
162
+ }
163
+ return pkgOrErr {pkg , err }
163
164
})
164
165
return nil
165
166
}
@@ -169,16 +170,14 @@ func (s *snapshot) TypeCheck(ctx context.Context, ids ...PackageID) ([]source.Pa
169
170
170
171
// -- await type-checking. --
171
172
//
172
- // Start a single goroutine for each promise.
173
+ // Start a single goroutine for each syntax package.
174
+ //
175
+ // Other promises are reached recursively, and will not be evaluated if they
176
+ // are not needed.
173
177
{
174
178
var g errgroup.Group
175
- // TODO(rfindley): find a good way to limit concurrency of type-checking,
176
- // which is CPU bound at this point.
177
- //
178
- // (calling g.SetLimit here is mostly ineffective, as promises are
179
- // recursively concurrent.)
180
- for _ , promise := range b .promises {
181
- promise := promise
179
+ for id := range b .needSyntax {
180
+ promise := b .promises [id ]
182
181
g .Go (func () error {
183
182
_ , err := promise .Get (ctx , nil )
184
183
return err
@@ -195,7 +194,7 @@ func (s *snapshot) TypeCheck(ctx context.Context, ids ...PackageID) ([]source.Pa
195
194
if pkgs [i ] != nil {
196
195
continue
197
196
}
198
- if err := b .imports [id ]. err ; err != nil {
197
+ if err := b .errors [id ]; err != nil {
199
198
if firstErr == nil {
200
199
firstErr = err
201
200
}
@@ -258,24 +257,29 @@ func (b *typeCheckBatch) parseFiles(ctx context.Context, fhs []source.FileHandle
258
257
// type-checking for syntax, depending on the requested syntax packages and
259
258
// available export data.
260
259
func (b * typeCheckBatch ) processPackage (ctx context.Context , ph * packageHandle ) (* types.Package , error ) {
261
- if err := b .awaitPredecessors (ctx , ph .m ); err != nil {
262
- return nil , err
263
- }
264
-
265
- // Wait to acquire CPU token.
260
+ // Type-check at most b.cpulimit syntax packages concurrently.
266
261
//
267
- // Note: it is important to acquire this token only after awaiting
268
- // predecessors, to avoid a starvation lock.
269
- select {
270
- case <- ctx .Done ():
271
- return nil , ctx .Err ()
272
- case b .cpulimit <- struct {}{}:
273
- defer func () {
274
- <- b .cpulimit // release CPU token
275
- }()
276
- }
277
-
262
+ // (For a non-syntax package, it is not necessary to await all
263
+ // predecessors because decoding export data typically only
264
+ // demands a small subset of all possible dependencies.)
278
265
if b .needSyntax [ph .m .ID ] {
266
+ if err := b .awaitPredecessors (ctx , ph .m ); err != nil {
267
+ return nil , err
268
+ }
269
+
270
+ // Wait to acquire CPU token.
271
+ //
272
+ // Note: it is important to acquire this token only after awaiting
273
+ // predecessors, to avoid a starvation lock.
274
+ select {
275
+ case <- ctx .Done ():
276
+ return nil , ctx .Err ()
277
+ case b .cpulimit <- struct {}{}:
278
+ defer func () {
279
+ <- b .cpulimit // release CPU token
280
+ }()
281
+ }
282
+
279
283
// We need a syntax package.
280
284
syntaxPkg , err := b .checkPackage (ctx , ph )
281
285
if err != nil {
@@ -306,35 +310,60 @@ func (b *typeCheckBatch) processPackage(ctx context.Context, ph *packageHandle)
306
310
// importPackage loads the given package from its export data in p.exportData
307
311
// (which must already be populated).
308
312
func (b * typeCheckBatch ) importPackage (ctx context.Context , m * source.Metadata , data []byte ) (* types.Package , error ) {
309
- impMap , errMap := b .importMap (m .ID )
310
- // Any failure to populate an import will cause confusing errors from
311
- // IImportShallow below.
312
- for path , err := range errMap {
313
- return nil , fmt .Errorf ("error importing %q for %q: %v" , path , m .ID , err )
313
+ impMap := b .importMap (m .ID )
314
+
315
+ var firstErr error
316
+ thisPackage := types .NewPackage (string (m .PkgPath ), string (m .Name ))
317
+ getPackage := func (path , name string ) * types.Package {
318
+ if path == string (m .PkgPath ) {
319
+ return thisPackage
320
+ }
321
+
322
+ promise := impMap [path ]
323
+ v , err := promise .Get (ctx , nil )
324
+ if err == nil {
325
+ result := v .(pkgOrErr )
326
+ err = result .err
327
+ if err == nil {
328
+ return result .pkg
329
+ }
330
+ }
331
+ // inv: err != nil
332
+ if firstErr == nil {
333
+ firstErr = err
334
+ }
335
+
336
+ // Context cancellation, or a very bad error such as a file permission
337
+ // error.
338
+ //
339
+ // Returning nil here will cause the import to fail (and panic if
340
+ // gcimporter.debug is set), but that is preferable to the confusing errors
341
+ // produced when shallow import encounters an empty package.
342
+ return nil
314
343
}
315
344
316
345
// TODO(rfindley): collect "deep" hashes here using the provided
317
346
// callback, for precise pruning.
318
- imported , err := gcimporter .IImportShallow (b .fset , gcimporter . GetPackageFromMap ( impMap ) , data , string (m .PkgPath ), func (* types.Package , string ) {})
347
+ imported , err := gcimporter .IImportShallow (b .fset , getPackage , data , string (m .PkgPath ), func (* types.Package , string ) {})
319
348
if err != nil {
320
- return nil , bug .Errorf ("invalid export data for %q: %v" , m .ID , err )
349
+ return nil , fmt .Errorf ("import failed for %q: %v" , m .ID , err )
321
350
}
322
351
return imported , nil
323
352
}
324
353
325
354
// checkPackageForImport type checks, but skips function bodies and does not
326
355
// record syntax information.
327
356
func (b * typeCheckBatch ) checkPackageForImport (ctx context.Context , ph * packageHandle ) (* types.Package , error ) {
328
- impMap , errMap := b .importMap (ph .inputs .id )
329
357
onError := func (e error ) {
330
358
// Ignore errors for exporting.
331
359
}
332
- cfg := b .typesConfig (ph .inputs , onError , impMap , errMap )
360
+ cfg := b .typesConfig (ctx , ph .inputs , onError )
361
+ cfg .IgnoreFuncBodies = true
362
+
333
363
pgfs , err := b .parseFiles (ctx , ph .inputs .compiledGoFiles )
334
364
if err != nil {
335
365
return nil , err
336
366
}
337
- cfg .IgnoreFuncBodies = true
338
367
pkg := types .NewPackage (string (ph .inputs .pkgPath ), string (ph .inputs .name ))
339
368
check := types .NewChecker (cfg , b .fset , pkg , nil )
340
369
@@ -404,32 +433,25 @@ func (b *typeCheckBatch) checkPackage(ctx context.Context, ph *packageHandle) (*
404
433
// error if awaiting failed due to context cancellation or if there was an
405
434
// unrecoverable error loading export data.
406
435
func (b * typeCheckBatch ) awaitPredecessors (ctx context.Context , m * source.Metadata ) error {
436
+ // await predecessors concurrently, as some of them may be non-syntax
437
+ // packages, and therefore will not have been started by the type-checking
438
+ // batch.
439
+ var g errgroup.Group
407
440
for _ , depID := range m .DepsByPkgPath {
408
- depID := depID
409
441
if p , ok := b .promises [depID ]; ok {
410
- if _ , err := p .Get (ctx , nil ); err != nil {
442
+ g .Go (func () error {
443
+ _ , err := p .Get (ctx , nil )
411
444
return err
412
- }
445
+ })
413
446
}
414
447
}
415
- return nil
448
+ return g . Wait ()
416
449
}
417
450
418
451
// importMap returns an import map for the given package ID, populated with
419
- // type-checked packages for its dependencies. It is intended for compatibility
420
- // with gcimporter.IImportShallow, so the first result uses the map signature
421
- // of that API, where keys are package path strings.
422
- //
423
- // importMap must only be used once all promises for dependencies of id have
424
- // been awaited.
425
- //
426
- // For any missing packages, importMap returns an entry in the resulting errMap
427
- // reporting the error for that package.
428
- //
429
- // Invariant: for all recursive dependencies, either impMap[path] or
430
- // errMap[path] is set.
431
- func (b * typeCheckBatch ) importMap (id PackageID ) (impMap map [string ]* types.Package , errMap map [PackagePath ]error ) {
432
- impMap = make (map [string ]* types.Package )
452
+ // promises keyed by package path for its dependencies.
453
+ func (b * typeCheckBatch ) importMap (id PackageID ) map [string ]* memoize.Promise {
454
+ impMap := make (map [string ]* memoize.Promise )
433
455
outerID := id
434
456
var populateDepsOf func (m * source.Metadata )
435
457
populateDepsOf = func (parent * source.Metadata ) {
@@ -438,35 +460,17 @@ func (b *typeCheckBatch) importMap(id PackageID) (impMap map[string]*types.Packa
438
460
if _ , ok := impMap [string (m .PkgPath )]; ok {
439
461
continue
440
462
}
441
- if _ , ok := errMap [m .PkgPath ]; ok {
442
- continue
443
- }
444
- b .mu .Lock ()
445
- result , ok := b .imports [m .ID ]
446
- b .mu .Unlock ()
463
+ promise , ok := b .promises [m .ID ]
447
464
if ! ok {
448
465
panic (fmt .Sprintf ("import map for %q missing package data for %q" , outerID , m .ID ))
449
466
}
450
- // We may fail to produce a package due to e.g. context cancellation
451
- // (handled elsewhere), or some catastrophic failure such as a package with
452
- // no files.
453
- switch {
454
- case result .err != nil :
455
- if errMap == nil {
456
- errMap = make (map [PackagePath ]error )
457
- }
458
- errMap [m .PkgPath ] = result .err
459
- case result .pkg != nil :
460
- impMap [string (m .PkgPath )] = result .pkg
461
- default :
462
- panic ("invalid import for " + id )
463
- }
467
+ impMap [string (m .PkgPath )] = promise
464
468
populateDepsOf (m )
465
469
}
466
470
}
467
471
m := b .meta .metadata [id ]
468
472
populateDepsOf (m )
469
- return impMap , errMap
473
+ return impMap
470
474
}
471
475
472
476
// packageData holds binary data (e.g. types, xrefs) extracted from a syntax
@@ -862,7 +866,6 @@ func typeCheckImpl(ctx context.Context, b *typeCheckBatch, inputs typeCheckInput
862
866
var goVersionRx = regexp .MustCompile (`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$` )
863
867
864
868
func doTypeCheck (ctx context.Context , b * typeCheckBatch , inputs typeCheckInputs ) (* syntaxPackage , error ) {
865
- impMap , errMap := b .importMap (inputs .id )
866
869
pkg := & syntaxPackage {
867
870
id : inputs .id ,
868
871
fset : b .fset , // must match parse call below
@@ -875,7 +878,6 @@ func doTypeCheck(ctx context.Context, b *typeCheckBatch, inputs typeCheckInputs)
875
878
Selections : make (map [* ast.SelectorExpr ]* types.Selection ),
876
879
Scopes : make (map [ast.Node ]* types.Scope ),
877
880
},
878
- importMap : impMap ,
879
881
}
880
882
typeparams .InitInstanceInfo (pkg .typesInfo )
881
883
@@ -916,7 +918,7 @@ func doTypeCheck(ctx context.Context, b *typeCheckBatch, inputs typeCheckInputs)
916
918
onError := func (e error ) {
917
919
pkg .typeErrors = append (pkg .typeErrors , e .(types.Error ))
918
920
}
919
- cfg := b .typesConfig (inputs , onError , impMap , errMap )
921
+ cfg := b .typesConfig (ctx , inputs , onError )
920
922
921
923
check := types .NewChecker (cfg , pkg .fset , pkg .types , pkg .typesInfo )
922
924
@@ -933,10 +935,26 @@ func doTypeCheck(ctx context.Context, b *typeCheckBatch, inputs typeCheckInputs)
933
935
if ctx .Err () != nil {
934
936
return nil , ctx .Err ()
935
937
}
938
+
939
+ // Collect imports by package path for the DependencyTypes API.
940
+ pkg .importMap = make (map [PackagePath ]* types.Package )
941
+ var collectDeps func (* types.Package )
942
+ collectDeps = func (p * types.Package ) {
943
+ pkgPath := PackagePath (p .Path ())
944
+ if _ , ok := pkg .importMap [pkgPath ]; ok {
945
+ return
946
+ }
947
+ pkg .importMap [pkgPath ] = p
948
+ for _ , imp := range p .Imports () {
949
+ collectDeps (imp )
950
+ }
951
+ }
952
+ collectDeps (pkg .types )
953
+
936
954
return pkg , nil
937
955
}
938
956
939
- func (b * typeCheckBatch ) typesConfig (inputs typeCheckInputs , onError func (e error ), impMap map [ string ] * types. Package , errMap map [ PackagePath ] error ) * types.Config {
957
+ func (b * typeCheckBatch ) typesConfig (ctx context. Context , inputs typeCheckInputs , onError func (e error )) * types.Config {
940
958
cfg := & types.Config {
941
959
Sizes : inputs .sizes ,
942
960
Error : onError ,
@@ -960,15 +978,19 @@ func (b *typeCheckBatch) typesConfig(inputs typeCheckInputs, onError func(e erro
960
978
if ! source .IsValidImport (inputs .pkgPath , depPH .m .PkgPath ) {
961
979
return nil , fmt .Errorf ("invalid use of internal package %q" , path )
962
980
}
963
- pkg , ok := impMap [ string ( depPH . m . PkgPath ) ]
981
+ promise , ok := b . promises [ id ]
964
982
if ! ok {
965
- err := errMap [depPH .m .PkgPath ]
966
- if err == nil {
967
- log .Fatalf ("neither pkg nor error is set" )
968
- }
983
+ panic ("missing import" )
984
+ }
985
+ v , err := promise .Get (ctx , nil )
986
+ if err != nil {
987
+ // Context cancellation: note that this could lead to non-deterministic
988
+ // results in the type-checker, so it is important that we don't use
989
+ // any type-checking results in the case where ctx is cancelled.
969
990
return nil , err
970
991
}
971
- return pkg , nil
992
+ res := v .(pkgOrErr )
993
+ return res .pkg , res .err
972
994
}),
973
995
}
974
996
0 commit comments