@@ -27,6 +27,7 @@ import (
27
27
"unicode"
28
28
"unicode/utf8"
29
29
30
+ "golang.org/x/sync/errgroup"
30
31
"golang.org/x/tools/go/ast/astutil"
31
32
"golang.org/x/tools/internal/event"
32
33
"golang.org/x/tools/internal/gocommand"
@@ -1140,8 +1141,8 @@ type Resolver interface {
1140
1141
// scan works with callback to search for packages. See scanCallback for details.
1141
1142
scan (ctx context.Context , callback * scanCallback ) error
1142
1143
1143
- // loadExports returns the set of exported symbols in the package at dir.
1144
- // loadExports may be called concurrently.
1144
+ // loadExports returns the package name and set of exported symbols in the
1145
+ // package at dir. loadExports may be called concurrently.
1145
1146
loadExports (ctx context.Context , pkg * pkg , includeTest bool ) (string , []stdlib.Symbol , error )
1146
1147
1147
1148
// scoreImportPath returns the relevance for an import path.
@@ -1218,54 +1219,52 @@ func addExternalCandidates(ctx context.Context, pass *pass, refs references, fil
1218
1219
imp * ImportInfo
1219
1220
pkg * packageInfo
1220
1221
}
1221
- results := make (chan result , len (refs ))
1222
+ results := make ([] * result , len (refs ))
1222
1223
1223
- ctx , cancel := context .WithCancel (ctx )
1224
- var wg sync.WaitGroup
1225
- defer func () {
1226
- cancel ()
1227
- wg .Wait ()
1228
- }()
1229
- var (
1230
- firstErr error
1231
- firstErrOnce sync.Once
1232
- )
1233
- for pkgName , symbols := range refs {
1234
- wg .Add (1 )
1235
- go func (pkgName string , symbols map [string ]bool ) {
1236
- defer wg .Done ()
1224
+ g , ctx := errgroup .WithContext (ctx )
1237
1225
1238
- found , err := findImport (ctx , pass , found [pkgName ], pkgName , symbols )
1226
+ searcher := symbolSearcher {
1227
+ logf : pass .env .logf ,
1228
+ srcDir : pass .srcDir ,
1229
+ xtest : strings .HasSuffix (pass .f .Name .Name , "_test" ),
1230
+ loadExports : resolver .loadExports ,
1231
+ }
1232
+
1233
+ i := 0
1234
+ for pkgName , symbols := range refs {
1235
+ index := i // claim an index in results
1236
+ i ++
1237
+ pkgName := pkgName
1238
+ symbols := symbols
1239
1239
1240
+ g .Go (func () error {
1241
+ found , err := searcher .search (ctx , found [pkgName ], pkgName , symbols )
1240
1242
if err != nil {
1241
- firstErrOnce .Do (func () {
1242
- firstErr = err
1243
- cancel ()
1244
- })
1245
- return
1243
+ return err
1246
1244
}
1247
-
1248
1245
if found == nil {
1249
- return // No matching package.
1246
+ return nil // No matching package.
1250
1247
}
1251
1248
1252
1249
imp := & ImportInfo {
1253
1250
ImportPath : found .importPathShort ,
1254
1251
}
1255
-
1256
1252
pkg := & packageInfo {
1257
1253
name : pkgName ,
1258
1254
exports : symbols ,
1259
1255
}
1260
- results <- result {imp , pkg }
1261
- }(pkgName , symbols )
1256
+ results [index ] = & result {imp , pkg }
1257
+ return nil
1258
+ })
1259
+ }
1260
+ if err := g .Wait (); err != nil {
1261
+ return err
1262
1262
}
1263
- go func () {
1264
- wg .Wait ()
1265
- close (results )
1266
- }()
1267
1263
1268
- for result := range results {
1264
+ for _ , result := range results {
1265
+ if result == nil {
1266
+ continue
1267
+ }
1269
1268
// Don't offer completions that would shadow predeclared
1270
1269
// names, such as github.com/coreos/etcd/error.
1271
1270
if types .Universe .Lookup (result .pkg .name ) != nil { // predeclared
@@ -1279,7 +1278,7 @@ func addExternalCandidates(ctx context.Context, pass *pass, refs references, fil
1279
1278
}
1280
1279
pass .addCandidate (result .imp , result .pkg )
1281
1280
}
1282
- return firstErr
1281
+ return nil
1283
1282
}
1284
1283
1285
1284
// notIdentifier reports whether ch is an invalid identifier character.
@@ -1669,39 +1668,55 @@ func sortSymbols(syms []stdlib.Symbol) {
1669
1668
})
1670
1669
}
1671
1670
1672
- // findImport searches for a package with the given symbols.
1673
- // If no package is found, findImport returns ("", false, nil)
1674
- func findImport (ctx context.Context , pass * pass , candidates []pkgDistance , pkgName string , symbols map [string ]bool ) (* pkg , error ) {
1671
+ // A symbolSearcher searches for a package with a set of symbols, among a set
1672
+ // of candidates. See [symbolSearcher.search].
1673
+ //
1674
+ // The search occurs within the scope of a single file, with context captured
1675
+ // in srcDir and xtest.
1676
+ type symbolSearcher struct {
1677
+ logf func (string , ... any )
1678
+ srcDir string // directory containing the file
1679
+ xtest bool // if set, the file containing is an x_test file
1680
+ loadExports func (ctx context.Context , pkg * pkg , includeTest bool ) (string , []stdlib.Symbol , error )
1681
+ }
1682
+
1683
+ // search searches the provided candidates for a package containing all
1684
+ // exported symbols.
1685
+ //
1686
+ // If successful, returns the resulting package.
1687
+ func (s * symbolSearcher ) search (ctx context.Context , candidates []pkgDistance , pkgName string , symbols map [string ]bool ) (* pkg , error ) {
1675
1688
// Sort the candidates by their import package length,
1676
1689
// assuming that shorter package names are better than long
1677
1690
// ones. Note that this sorts by the de-vendored name, so
1678
1691
// there's no "penalty" for vendoring.
1679
1692
sort .Sort (byDistanceOrImportPathShortLength (candidates ))
1680
- if pass . env . Logf != nil {
1693
+ if s . logf != nil {
1681
1694
for i , c := range candidates {
1682
- pass . env . Logf ("%s candidate %d/%d: %v in %v" , pkgName , i + 1 , len (candidates ), c .pkg .importPathShort , c .pkg .dir )
1695
+ s . logf ("%s candidate %d/%d: %v in %v" , pkgName , i + 1 , len (candidates ), c .pkg .importPathShort , c .pkg .dir )
1683
1696
}
1684
1697
}
1685
- resolver , err := pass .env .GetResolver ()
1686
- if err != nil {
1687
- return nil , err
1688
- }
1689
1698
1690
- // Collect exports for packages with matching names.
1699
+ // Arrange rescv so that we can we can await results in order of relevance
1700
+ // and exit as soon as we find the first match.
1701
+ //
1702
+ // Search with bounded concurrency, returning as soon as the first result
1703
+ // among rescv is non-nil.
1691
1704
rescv := make ([]chan * pkg , len (candidates ))
1692
1705
for i := range candidates {
1693
1706
rescv [i ] = make (chan * pkg , 1 )
1694
1707
}
1695
1708
const maxConcurrentPackageImport = 4
1696
1709
loadExportsSem := make (chan struct {}, maxConcurrentPackageImport )
1697
1710
1711
+ // Ensure that all work is completed at exit.
1698
1712
ctx , cancel := context .WithCancel (ctx )
1699
1713
var wg sync.WaitGroup
1700
1714
defer func () {
1701
1715
cancel ()
1702
1716
wg .Wait ()
1703
1717
}()
1704
1718
1719
+ // Start the search.
1705
1720
wg .Add (1 )
1706
1721
go func () {
1707
1722
defer wg .Done ()
@@ -1712,51 +1727,67 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa
1712
1727
return
1713
1728
}
1714
1729
1730
+ i := i
1731
+ c := c
1715
1732
wg .Add (1 )
1716
- go func (c pkgDistance , resc chan <- * pkg ) {
1733
+ go func () {
1717
1734
defer func () {
1718
1735
<- loadExportsSem
1719
1736
wg .Done ()
1720
1737
}()
1721
-
1722
- pass .env .logf ("loading exports in dir %s (seeking package %s)" , c .pkg .dir , pkgName )
1723
- // If we're an x_test, load the package under test's test variant.
1724
- includeTest := strings .HasSuffix (pass .f .Name .Name , "_test" ) && c .pkg .dir == pass .srcDir
1725
- _ , exports , err := resolver .loadExports (ctx , c .pkg , includeTest )
1726
- if err != nil {
1727
- pass .env .logf ("loading exports in dir %s (seeking package %s): %v" , c .pkg .dir , pkgName , err )
1728
- resc <- nil
1729
- return
1738
+ if s .logf != nil {
1739
+ s .logf ("loading exports in dir %s (seeking package %s)" , c .pkg .dir , pkgName )
1730
1740
}
1731
-
1732
- exportsMap := make (map [string ]bool , len (exports ))
1733
- for _ , sym := range exports {
1734
- exportsMap [sym .Name ] = true
1735
- }
1736
-
1737
- // If it doesn't have the right
1738
- // symbols, send nil to mean no match.
1739
- for symbol := range symbols {
1740
- if ! exportsMap [symbol ] {
1741
- resc <- nil
1742
- return
1741
+ pkg , err := s .searchOne (ctx , c , symbols )
1742
+ if err != nil {
1743
+ if s .logf != nil && ctx .Err () == nil {
1744
+ s .logf ("loading exports in dir %s (seeking package %s): %v" , c .pkg .dir , pkgName , err )
1743
1745
}
1746
+ pkg = nil
1744
1747
}
1745
- resc <- c . pkg
1746
- }(c , rescv [ i ] )
1748
+ rescv [ i ] <- pkg // may be nil
1749
+ }()
1747
1750
}
1748
1751
}()
1749
1752
1753
+ // Await the first (best) result.
1750
1754
for _ , resc := range rescv {
1751
- pkg := <- resc
1752
- if pkg == nil {
1753
- continue
1755
+ select {
1756
+ case r := <- resc :
1757
+ if r != nil {
1758
+ return r , nil
1759
+ }
1760
+ case <- ctx .Done ():
1761
+ return nil , ctx .Err ()
1754
1762
}
1755
- return pkg , nil
1756
1763
}
1757
1764
return nil , nil
1758
1765
}
1759
1766
1767
+ func (s * symbolSearcher ) searchOne (ctx context.Context , c pkgDistance , symbols map [string ]bool ) (* pkg , error ) {
1768
+ if ctx .Err () != nil {
1769
+ return nil , ctx .Err ()
1770
+ }
1771
+ // If we're considering the package under test from an x_test, load the
1772
+ // test variant.
1773
+ includeTest := s .xtest && c .pkg .dir == s .srcDir
1774
+ _ , exports , err := s .loadExports (ctx , c .pkg , includeTest )
1775
+ if err != nil {
1776
+ return nil , err
1777
+ }
1778
+
1779
+ exportsMap := make (map [string ]bool , len (exports ))
1780
+ for _ , sym := range exports {
1781
+ exportsMap [sym .Name ] = true
1782
+ }
1783
+ for symbol := range symbols {
1784
+ if ! exportsMap [symbol ] {
1785
+ return nil , nil // no match
1786
+ }
1787
+ }
1788
+ return c .pkg , nil
1789
+ }
1790
+
1760
1791
// pkgIsCandidate reports whether pkg is a candidate for satisfying the
1761
1792
// finding which package pkgIdent in the file named by filename is trying
1762
1793
// to refer to.
0 commit comments