@ -16,13 +16,13 @@ import (
"go/scanner"
"go/token"
"go/types"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"golang.org/x/sync/errgroup"
@ -31,7 +31,6 @@ import (
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/packagesinternal"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// A LoadMode controls the amount of detail to return when loading.
@ -44,6 +43,20 @@ import (
// ID and Errors (if present) will always be filled.
// [Load] may return more information than requested.
//
// The Mode flag is a union of several bits named NeedName,
// NeedFiles, and so on, each of which determines whether
// a given field of Package (Name, Files, etc) should be
// populated.
//
// For convenience, we provide named constants for the most
// common combinations of Need flags:
//
// [LoadFiles] lists of files in each package
// [LoadImports] ... plus imports
// [LoadTypes] ... plus type information
// [LoadSyntax] ... plus type-annotated syntax
// [LoadAllSyntax] ... for all dependencies
//
// Unfortunately there are a number of open bugs related to
// interactions among the LoadMode bits:
// - https://github.com/golang/go/issues/56633
@ -56,7 +69,7 @@ const (
// NeedName adds Name and PkgPath.
NeedName LoadMode = 1 << iota
// NeedFiles adds GoFiles and OtherFiles.
// NeedFiles adds Dir, GoFiles, OtherFiles, and IgnoredFiles
NeedFiles
// NeedCompiledGoFiles adds CompiledGoFiles.
@ -78,7 +91,7 @@ const (
// NeedSyntax adds Syntax and Fset.
NeedSyntax
// NeedTypesInfo adds TypesInfo.
// NeedTypesInfo adds TypesInfo and Fset .
NeedTypesInfo
// NeedTypesSizes adds TypesSizes.
@ -87,9 +100,10 @@ const (
// needInternalDepsErrors adds the internal deps errors field for use by gopls.
needInternalDepsErrors
// needInternalForTest adds the internal forTest field.
// NeedForTest adds ForTest.
//
// Tests must also be set on the context for this field to be populated.
needInternal ForTest
Need ForTest
// typecheckCgo enables full support for type checking cgo. Requires Go 1.15+.
// Modifies CompiledGoFiles and Types, and has no effect on its own.
@ -109,33 +123,18 @@ const (
const (
// LoadFiles loads the name and file names for the initial packages.
//
// Deprecated: LoadFiles exists for historical compatibility
// and should not be used. Please directly specify the needed fields using the Need values.
LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles
// LoadImports loads the name, file names, and import mapping for the initial packages.
//
// Deprecated: LoadImports exists for historical compatibility
// and should not be used. Please directly specify the needed fields using the Need values.
LoadImports = LoadFiles | NeedImports
// LoadTypes loads exported type information for the initial packages.
//
// Deprecated: LoadTypes exists for historical compatibility
// and should not be used. Please directly specify the needed fields using the Need values.
LoadTypes = LoadImports | NeedTypes | NeedTypesSizes
// LoadSyntax loads typed syntax for the initial packages.
//
// Deprecated: LoadSyntax exists for historical compatibility
// and should not be used. Please directly specify the needed fields using the Need values.
LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo
// LoadAllSyntax loads typed syntax for the initial packages and all dependencies.
//
// Deprecated: LoadAllSyntax exists for historical compatibility
// and should not be used. Please directly specify the needed fields using the Need values.
LoadAllSyntax = LoadSyntax | NeedDeps
// Deprecated: NeedExportsFile is a historical misspelling of NeedExportFile.
@ -145,13 +144,7 @@ const (
// A Config specifies details about how packages should be loaded.
// The zero value is a valid configuration.
//
// Calls to Load do not modify this struct.
//
// TODO(adonovan): #67702: this is currently false: in fact,
// calls to [Load] do not modify the public fields of this struct, but
// may modify hidden fields, so concurrent calls to [Load] must not
// use the same Config. But perhaps we should reestablish the
// documented invariant.
// Calls to [Load] do not modify this struct.
type Config struct {
// Mode controls the level of information returned for each package.
Mode LoadMode
@ -182,19 +175,10 @@ type Config struct {
//
Env [ ] string
// gocmdRunner guards go command calls from concurrency errors.
gocmdRunner * gocommand . Runner
// BuildFlags is a list of command-line flags to be passed through to
// the build system's query tool.
BuildFlags [ ] string
// modFile will be used for -modfile in go command invocations.
modFile string
// modFlag will be used for -modfile in go command invocations.
modFlag string
// Fset provides source position information for syntax trees and types.
// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
Fset * token . FileSet
@ -241,9 +225,13 @@ type Config struct {
// drivers may vary in their level of support for overlays.
Overlay map [ string ] [ ] byte
// goListOverlayFile is the JSON file that encodes the Overlay
// mapping, used by 'go list -overlay=...'
goListOverlayFile string
// -- Hidden configuration fields only for use in x/tools --
// modFile will be used for -modfile in go command invocations.
modFile string
// modFlag will be used for -modfile in go command invocations.
modFlag string
}
// Load loads and returns the Go packages named by the given patterns.
@ -334,21 +322,24 @@ func defaultDriver(cfg *Config, patterns ...string) (*DriverResponse, bool, erro
} else if ! response . NotHandled {
return response , true , nil
}
// (fall through)
// not handled: fall through
}
// go list fallback
//
// Write overlays once, as there are many calls
// to 'go list' (one per chunk plus others too).
overlay , cleanupOverlay , err := gocommand . WriteOverlays ( cfg . Overlay )
overlayFile , cleanupOverlay , err := gocommand . WriteOverlays ( cfg . Overlay )
if err != nil {
return nil , false , err
}
defer cleanupOverlay ( )
cfg . goListOverlayFile = overlay
response , err := callDriverOnChunks ( goListDriver , cfg , chunks )
var runner gocommand . Runner // (shared across many 'go list' calls)
driver := func ( cfg * Config , patterns [ ] string ) ( * DriverResponse , error ) {
return goListDriver ( cfg , & runner , overlayFile , patterns )
}
response , err := callDriverOnChunks ( driver , cfg , chunks )
if err != nil {
return nil , false , err
}
@ -386,16 +377,14 @@ func splitIntoChunks(patterns []string, argMax int) ([][]string, error) {
func callDriverOnChunks ( driver driver , cfg * Config , chunks [ ] [ ] string ) ( * DriverResponse , error ) {
if len ( chunks ) == 0 {
return driver ( cfg )
return driver ( cfg , nil )
}
responses := make ( [ ] * DriverResponse , len ( chunks ) )
errNotHandled := errors . New ( "driver returned NotHandled" )
var g errgroup . Group
for i , chunk := range chunks {
i := i
chunk := chunk
g . Go ( func ( ) ( err error ) {
responses [ i ] , err = driver ( cfg , chunk ... )
responses [ i ] , err = driver ( cfg , chunk )
if responses [ i ] != nil && responses [ i ] . NotHandled {
err = errNotHandled
}
@ -445,6 +434,12 @@ type Package struct {
// PkgPath is the package path as used by the go/types package.
PkgPath string
// Dir is the directory associated with the package, if it exists.
//
// For packages listed by the go command, this is the directory containing
// the package files.
Dir string
// Errors contains any errors encountered querying the metadata
// of the package, or while parsing or type-checking its files.
Errors [ ] Error
@ -532,8 +527,8 @@ type Package struct {
// -- internal --
// f orTest is the package under test, if any.
f orTest string
// F orTest is the package under test, if any.
F orTest string
// depsErrors is the DepsErrors field from the go list response, if any.
depsErrors [ ] * packagesinternal . PackageError
@ -562,9 +557,6 @@ type ModuleError struct {
}
func init ( ) {
packagesinternal . GetForTest = func ( p interface { } ) string {
return p . ( * Package ) . forTest
}
packagesinternal . GetDepsErrors = func ( p interface { } ) [ ] * packagesinternal . PackageError {
return p . ( * Package ) . depsErrors
}
@ -576,7 +568,6 @@ func init() {
}
packagesinternal . TypecheckCgo = int ( typecheckCgo )
packagesinternal . DepsErrors = int ( needInternalDepsErrors )
packagesinternal . ForTest = int ( needInternalForTest )
}
// An Error describes a problem with a package's metadata, syntax, or types.
@ -692,18 +683,19 @@ func (p *Package) String() string { return p.ID }
// loaderPackage augments Package with state used during the loading phase
type loaderPackage struct {
* Package
importErrors map [ string ] error // maps each bad import to its error
loadOnce sync . Once
color uint8 // for cycle detection
needsrc bool // load from source (Mode >= LoadTypes)
needtypes bool // type information is either requested or depended on
initial bool // package was matched by a pattern
goVersion int // minor version number of go command on PATH
importErrors map [ string ] error // maps each bad import to its error
preds [ ] * loaderPackage // packages that import this one
unfinishedSuccs atomic . Int32 // number of direct imports not yet loaded
color uint8 // for cycle detection
needsrc bool // load from source (Mode >= LoadTypes)
needtypes bool // type information is either requested or depended on
initial bool // package was matched by a pattern
goVersion int // minor version number of go command on PATH
}
// loader holds the working state of a single call to load.
type loader struct {
pkgs map [ string ] * loaderPackage
pkgs map [ string ] * loaderPackage // keyed by Package.ID
Config
sizes types . Sizes // non-nil if needed by mode
parseCache map [ string ] * parseValue
@ -749,9 +741,6 @@ func newLoader(cfg *Config) *loader {
if ld . Config . Env == nil {
ld . Config . Env = os . Environ ( )
}
if ld . Config . gocmdRunner == nil {
ld . Config . gocmdRunner = & gocommand . Runner { }
}
if ld . Context == nil {
ld . Context = context . Background ( )
}
@ -765,7 +754,7 @@ func newLoader(cfg *Config) *loader {
ld . requestedMode = ld . Mode
ld . Mode = impliedLoadMode ( ld . Mode )
if ld . Mode & NeedTypes != 0 || ld . Mode & NeedSyntax != 0 {
if ld . Mode & ( NeedSyntax | NeedTypes | NeedTypesInfo ) != 0 {
if ld . Fset == nil {
ld . Fset = token . NewFileSet ( )
}
@ -806,7 +795,7 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
exportDataInvalid := len ( ld . Overlay ) > 0 || pkg . ExportFile == "" && pkg . PkgPath != "unsafe"
// This package needs type information if the caller requested types and the package is
// either a root, or it's a non-root and the user requested dependencies ...
needtypes := ( ld . Mode & NeedTypes | NeedTypesInfo != 0 && ( rootIndex >= 0 || ld . Mode & NeedDeps != 0 ) )
needtypes := ( ld . Mode & ( NeedTypes | NeedTypesInfo ) != 0 && ( rootIndex >= 0 || ld . Mode & NeedDeps != 0 ) )
// This package needs source if the call requested source (or types info, which implies source)
// and the package is either a root, or itas a non- root and the user requested dependencies...
needsrc := ( ( ld . Mode & ( NeedSyntax | NeedTypesInfo ) != 0 && ( rootIndex >= 0 || ld . Mode & NeedDeps != 0 ) ) ||
@ -831,9 +820,10 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
}
}
if ld . Mode & NeedImports != 0 {
// Materialize the import graph.
// Materialize the import graph if it is needed (NeedImports),
// or if we'll be using loadPackages (Need{Syntax|Types|TypesInfo}).
var leaves [ ] * loaderPackage // packages with no unfinished successors
if ld . Mode & ( NeedImports | NeedSyntax | NeedTypes | NeedTypesInfo ) != 0 {
const (
white = 0 // new
grey = 1 // in progress
@ -852,63 +842,76 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
// dependency on a package that does. These are the only packages
// for which we load source code.
var stack [ ] * loaderPackage
var visit func ( lpkg * loaderPackage ) bool
visit = func ( lpkg * loaderPackage ) bool {
switch lpkg . color {
case black :
return lpkg . needsrc
case grey :
var visit func ( from , lpkg * loaderPackage ) bool
visit = func ( from , lpkg * loaderPackage ) bool {
if lpkg . color == grey {
panic ( "internal error: grey node" )
}
lpkg . color = grey
stack = append ( stack , lpkg ) // push
stubs := lpkg . Imports // the structure form has only stubs with the ID in the Imports
lpkg . Imports = make ( map [ string ] * Package , len ( stubs ) )
for importPath , ipkg := range stubs {
var importErr error
imp := ld . pkgs [ ipkg . ID ]
if imp == nil {
// (includes package "C" when DisableCgo)
importErr = fmt . Errorf ( "missing package: %q" , ipkg . ID )
} else if imp . color == grey {
importErr = fmt . Errorf ( "import cycle: %s" , stack )
if lpkg . color == white {
lpkg . color = grey
stack = append ( stack , lpkg ) // push
stubs := lpkg . Imports // the structure form has only stubs with the ID in the Imports
lpkg . Imports = make ( map [ string ] * Package , len ( stubs ) )
for importPath , ipkg := range stubs {
var importErr error
imp := ld . pkgs [ ipkg . ID ]
if imp == nil {
// (includes package "C" when DisableCgo)
importErr = fmt . Errorf ( "missing package: %q" , ipkg . ID )
} else if imp . color == grey {
importErr = fmt . Errorf ( "import cycle: %s" , stack )
}
if importErr != nil {
if lpkg . importErrors == nil {
lpkg . importErrors = make ( map [ string ] error )
}
lpkg . importErrors [ importPath ] = importErr
continue
}
if visit ( lpkg , imp ) {
lpkg . needsrc = true
}
lpkg . Imports [ importPath ] = imp . Package
}
if importErr != nil {
if lpkg . importErrors == nil {
lpkg . importErrors = make ( map [ string ] error )
// -- postorder --
// Complete type information is required for the
// immediate dependencies of each source package.
if lpkg . needsrc && ld . Mode & NeedTypes != 0 {
for _ , ipkg := range lpkg . Imports {
ld . pkgs [ ipkg . ID ] . needtypes = true
}
lpkg . importErrors [ importPath ] = importErr
continue
}
if visit ( imp ) {
lpkg . needsrc = true
// NeedTypeSizes causes TypeSizes to be set even
// on packages for which types aren't needed.
if ld . Mode & NeedTypesSizes != 0 {
lpkg . TypesSizes = ld . sizes
}
lpkg . Imports [ importPath ] = imp . Package
}
// Complete type information is required for the
// immediate dependencies of each source package.
if lpkg . needsrc && ld . Mode & NeedTypes != 0 {
for _ , ipkg := range lpkg . Imports {
ld . pkgs [ ipkg . ID ] . needtypes = true
// Add packages with no imports directly to the queue of leaves.
if len ( lpkg . Imports ) == 0 {
leaves = append ( leaves , lpkg )
}
stack = stack [ : len ( stack ) - 1 ] // pop
lpkg . color = black
}
// NeedTypeSizes causes TypeSizes to be set even
// on packages for which types aren't needed.
if ld . Mode & NeedTypesSizes != 0 {
lpkg . TypesSizes = ld . sizes
// Add edge from predecessor.
if from != nil {
from . unfinishedSuccs . Add ( + 1 ) // incref
lpkg . preds = append ( lpkg . preds , from )
}
stack = stack [ : len ( stack ) - 1 ] // pop
lpkg . color = black
return lpkg . needsrc
}
// For each initial package, create its import DAG.
for _ , lpkg := range initial {
visit ( lpkg )
visit ( nil , lpkg )
}
} else {
@ -921,16 +924,45 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
// Load type data and syntax if needed, starting at
// the initial packages (roots of the import DAG).
if ld . Mode & NeedTypes != 0 || ld . Mode & NeedSyntax != 0 {
var wg sync . WaitGroup
for _ , lpkg := range initial {
wg . Add ( 1 )
go func ( lpkg * loaderPackage ) {
ld . loadRecursive ( lpkg )
wg . Done ( )
} ( lpkg )
if ld . Mode & ( NeedSyntax | NeedTypes | NeedTypesInfo ) != 0 {
// We avoid using g.SetLimit to limit concurrency as
// it makes g.Go stop accepting work, which prevents
// workers from enqeuing, and thus finishing, and thus
// allowing the group to make progress: deadlock.
//
// Instead we use the ioLimit and cpuLimit semaphores.
g , _ := errgroup . WithContext ( ld . Context )
// enqueues adds a package to the type-checking queue.
// It must have no unfinished successors.
var enqueue func ( * loaderPackage )
enqueue = func ( lpkg * loaderPackage ) {
g . Go ( func ( ) error {
// Parse and type-check.
ld . loadPackage ( lpkg )
// Notify each waiting predecessor,
// and enqueue it when it becomes a leaf.
for _ , pred := range lpkg . preds {
if pred . unfinishedSuccs . Add ( - 1 ) == 0 { // decref
enqueue ( pred )
}
}
return nil
} )
}
// Load leaves first, adding new packages
// to the queue as they become leaves.
for _ , leaf := range leaves {
enqueue ( leaf )
}
if err := g . Wait ( ) ; err != nil {
return nil , err // cancelled
}
wg . Wait ( )
}
// If the context is done, return its error and
@ -977,7 +1009,7 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
if ld . requestedMode & NeedSyntax == 0 {
ld . pkgs [ i ] . Syntax = nil
}
if ld . requestedMode & NeedTypes == 0 && ld . requestedMode & NeedSyntax == 0 {
if ld . requestedMode & ( NeedSyntax | NeedTypes | NeedTypesInfo ) == 0 {
ld . pkgs [ i ] . Fset = nil
}
if ld . requestedMode & NeedTypesInfo == 0 {
@ -994,31 +1026,10 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) {
return result , nil
}
// loadRecursive loads the specified package and its dependencies,
// recursively, in parallel, in topological order.
// It is atomic and idempotent.
// Precondition: ld.Mode&NeedTypes.
func ( ld * loader ) loadRecursive ( lpkg * loaderPackage ) {
lpkg . loadOnce . Do ( func ( ) {
// Load the direct dependencies, in parallel.
var wg sync . WaitGroup
for _ , ipkg := range lpkg . Imports {
imp := ld . pkgs [ ipkg . ID ]
wg . Add ( 1 )
go func ( imp * loaderPackage ) {
ld . loadRecursive ( imp )
wg . Done ( )
} ( imp )
}
wg . Wait ( )
ld . loadPackage ( lpkg )
} )
}
// loadPackage loads the specified package.
// loadPackage loads/parses/typechecks the specified package.
// It must be called only once per Package,
// after immediate dependencies are loaded.
// Precondition: ld.Mode & NeedTypes .
// Precondition: ld.Mode&(NeedSyntax|NeedTypes|NeedTypesInfo) != 0.
func ( ld * loader ) loadPackage ( lpkg * loaderPackage ) {
if lpkg . PkgPath == "unsafe" {
// Fill in the blanks to avoid surprises.
@ -1054,6 +1065,10 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
if ! lpkg . needtypes && ! lpkg . needsrc {
return
}
// TODO(adonovan): this condition looks wrong:
// I think it should be lpkg.needtypes && !lpg.needsrc,
// so that NeedSyntax without NeedTypes can be satisfied by export data.
if ! lpkg . needsrc {
if err := ld . loadFromExportData ( lpkg ) ; err != nil {
lpkg . Errors = append ( lpkg . Errors , Error {
@ -1159,7 +1174,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
}
lpkg . Syntax = files
if ld . Config . Mode & NeedTypes == 0 {
if ld . Config . Mode & ( NeedTypes | NeedTypesInfo ) == 0 {
return
}
@ -1170,16 +1185,20 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
return
}
lpkg . TypesInfo = & types . Info {
Types : make ( map [ ast . Expr ] types . TypeAndValue ) ,
Defs : make ( map [ * ast . Ident ] types . Object ) ,
Uses : make ( map [ * ast . Ident ] types . Object ) ,
Implicits : make ( map [ ast . Node ] types . Object ) ,
Instances : make ( map [ * ast . Ident ] types . Instance ) ,
Scopes : make ( map [ ast . Node ] * types . Scope ) ,
Selections : make ( map [ * ast . SelectorExpr ] * types . Selection ) ,
// Populate TypesInfo only if needed, as it
// causes the type checker to work much harder.
if ld . Config . Mode & NeedTypesInfo != 0 {
lpkg . TypesInfo = & types . Info {
Types : make ( map [ ast . Expr ] types . TypeAndValue ) ,
Defs : make ( map [ * ast . Ident ] types . Object ) ,
Uses : make ( map [ * ast . Ident ] types . Object ) ,
Implicits : make ( map [ ast . Node ] types . Object ) ,
Instances : make ( map [ * ast . Ident ] types . Instance ) ,
Scopes : make ( map [ ast . Node ] * types . Scope ) ,
Selections : make ( map [ * ast . SelectorExpr ] * types . Selection ) ,
FileVersions : make ( map [ * ast . File ] string ) ,
}
}
versions . InitFileVersions ( lpkg . TypesInfo )
lpkg . TypesSizes = ld . sizes
importer := importerFunc ( func ( path string ) ( * types . Package , error ) {
@ -1232,6 +1251,10 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
}
}
// Type-checking is CPU intensive.
cpuLimit <- unit { } // acquire a token
defer func ( ) { <- cpuLimit } ( ) // release a token
typErr := types . NewChecker ( tc , ld . Fset , lpkg . Types , lpkg . TypesInfo ) . Files ( lpkg . Syntax )
lpkg . importErrors = nil // no longer needed
@ -1296,8 +1319,11 @@ type importerFunc func(path string) (*types.Package, error)
func ( f importerFunc ) Import ( path string ) ( * types . Package , error ) { return f ( path ) }
// We use a counting semaphore to limit
// the number of parallel I/O calls per process.
var ioLimit = make ( chan bool , 20 )
// the number of parallel I/O calls or CPU threads per process.
var (
ioLimit = make ( chan unit , 20 )
cpuLimit = make ( chan unit , runtime . GOMAXPROCS ( 0 ) )
)
func ( ld * loader ) parseFile ( filename string ) ( * ast . File , error ) {
ld . parseCacheMu . Lock ( )
@ -1314,20 +1340,28 @@ func (ld *loader) parseFile(filename string) (*ast.File, error) {
var src [ ] byte
for f , contents := range ld . Config . Overlay {
// TODO(adonovan): Inefficient for large overlays.
// Do an exact name-based map lookup
// (for nonexistent files) followed by a
// FileID-based map lookup (for existing ones).
if sameFile ( f , filename ) {
src = contents
break
}
}
var err error
if src == nil {
ioLimit <- true // wait
ioLimit <- unit { } // acquire a token
src , err = os . ReadFile ( filename )
<- ioLimit // signal
<- ioLimit // release a token
}
if err != nil {
v . err = err
} else {
// Parsing is CPU intensive.
cpuLimit <- unit { } // acquire a token
v . f , v . err = ld . ParseFile ( ld . Fset , filename , src )
<- cpuLimit // release a token
}
close ( v . ready )
@ -1342,18 +1376,21 @@ func (ld *loader) parseFile(filename string) (*ast.File, error) {
// Because files are scanned in parallel, the token.Pos
// positions of the resulting ast.Files are not ordered.
func ( ld * loader ) parseFiles ( filenames [ ] string ) ( [ ] * ast . File , [ ] error ) {
var wg sync . WaitGroup
n := len ( filenames )
parsed := make ( [ ] * ast . File , n )
errors := make ( [ ] error , n )
for i , file := range filenames {
wg . Add ( 1 )
go func ( i int , filename string ) {
var (
n = len ( filenames )
parsed = make ( [ ] * ast . File , n )
errors = make ( [ ] error , n )
)
var g errgroup . Group
for i , filename := range filenames {
// This creates goroutines unnecessarily in the
// cache-hit case, but that case is uncommon.
g . Go ( func ( ) error {
parsed [ i ] , errors [ i ] = ld . parseFile ( filename )
wg . Done ( )
} ( i , file )
return nil
} )
}
w g. Wait ( )
g . Wait ( )
// Eliminate nils, preserving order.
var o int
@ -1524,4 +1561,4 @@ func usesExportData(cfg *Config) bool {
return cfg . Mode & NeedExportFile != 0 || cfg . Mode & NeedTypes != 0 && cfg . Mode & NeedDeps == 0
}
var _ interface { } = io . Discard // assert build toolchain is go1.16 or later
type unit struct { }