Skip to content

Commit 63f81c9

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/cache/metadata: use pointers for Graph indexes
As noted in a recent CL, using pointers in the metadata graph is both simpler and more efficient, and avoids some unnecessarily defensive code that was looking up IDs. Change-Id: I8a45e78c4f9be88ae3460ec77189ca43dda163dd Reviewed-on: https://go-review.googlesource.com/c/tools/+/686839 Auto-Submit: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent b2cd89d commit 63f81c9

File tree

8 files changed

+80
-83
lines changed

8 files changed

+80
-83
lines changed

gopls/internal/cache/load.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,8 +792,8 @@ func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool {
792792
n := len(mp.CompiledGoFiles)
793793
checkURIs:
794794
for _, uri := range slices.Concat(mp.CompiledGoFiles[0:n:n], mp.GoFiles) {
795-
for _, id := range g.IDs[uri] {
796-
if !metadata.IsCommandLineArguments(id) {
795+
for _, pkg := range g.ForFile[uri] {
796+
if !metadata.IsCommandLineArguments(pkg.ID) {
797797
continue checkURIs
798798
}
799799
}

gopls/internal/cache/metadata/graph.go

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,20 @@ type Graph struct {
2222
// Packages maps package IDs to their associated Packages.
2323
Packages map[PackageID]*Package
2424

25+
// Each of the three maps below is an index of the pointer values held
26+
// by the Packages map. However, Package pointers are not generally canonical.
27+
2528
// ImportedBy maps package IDs to the list of packages that import them.
26-
ImportedBy map[PackageID][]PackageID
29+
ImportedBy map[PackageID][]*Package
2730

28-
// ByPackagePath maps package by their package path to their package ID.
31+
// ForPackagePath maps package by their package path to their package ID.
2932
// Non-test packages appear before test packages, and within each of those
3033
// categories, packages with fewer CompiledGoFiles appear first.
31-
//
32-
// TODO(rfindley): there's no reason for ImportedBy and IDs to not also hold
33-
// pointers, rather than IDs, and pointers are more convenient.
34-
ByPackagePath map[PackagePath][]*Package
34+
ForPackagePath map[PackagePath][]*Package
3535

36-
// IDs maps file URIs to package IDs, sorted by (!valid, cli, packageID).
36+
// ForFile maps file URIs to packages, sorted by (!valid, cli, packageID).
3737
// A single file may belong to multiple packages due to tests packages.
38-
//
39-
// Invariant: all IDs present in the IDs map exist in the metadata map.
40-
IDs map[protocol.DocumentURI][]PackageID
38+
ForFile map[protocol.DocumentURI][]*Package
4139
}
4240

4341
// Metadata implements the [Source] interface
@@ -93,18 +91,18 @@ func (g *Graph) Update(updates map[PackageID]*Package) *Graph {
9391
// deriving relations from the specified metadata.
9492
func newGraph(pkgs map[PackageID]*Package) *Graph {
9593
// Build the import graph.
96-
importedBy := make(map[PackageID][]PackageID)
94+
importedBy := make(map[PackageID][]*Package)
9795
byPackagePath := make(map[PackagePath][]*Package)
98-
for id, mp := range pkgs {
96+
for _, mp := range pkgs {
9997
for _, depID := range mp.DepsByPkgPath {
100-
importedBy[depID] = append(importedBy[depID], id)
98+
importedBy[depID] = append(importedBy[depID], mp)
10199
}
102100
byPackagePath[mp.PkgPath] = append(byPackagePath[mp.PkgPath], mp)
103101
}
104102

105103
// Collect file associations.
106-
uriIDs := make(map[protocol.DocumentURI][]PackageID)
107-
for id, mp := range pkgs {
104+
uriPkgs := make(map[protocol.DocumentURI][]*Package)
105+
for _, mp := range pkgs {
108106
uris := map[protocol.DocumentURI]struct{}{}
109107
for _, uri := range mp.CompiledGoFiles {
110108
uris[uri] = struct{}{}
@@ -118,34 +116,34 @@ func newGraph(pkgs map[PackageID]*Package) *Graph {
118116
}
119117
}
120118
for uri := range uris {
121-
uriIDs[uri] = append(uriIDs[uri], id)
119+
uriPkgs[uri] = append(uriPkgs[uri], mp)
122120
}
123121
}
124122

125123
// Sort and filter file associations.
126-
for uri, ids := range uriIDs {
127-
sort.Slice(ids, func(i, j int) bool {
128-
cli := IsCommandLineArguments(ids[i])
129-
clj := IsCommandLineArguments(ids[j])
124+
for uri, pkgs := range uriPkgs {
125+
sort.Slice(pkgs, func(i, j int) bool {
126+
cli := IsCommandLineArguments(pkgs[i].ID)
127+
clj := IsCommandLineArguments(pkgs[j].ID)
130128
if cli != clj {
131129
return clj
132130
}
133131

134132
// 2. packages appear in name order.
135-
return ids[i] < ids[j]
133+
return pkgs[i].ID < pkgs[j].ID
136134
})
137135

138-
// Choose the best IDs for each URI, according to the following rules:
136+
// Choose the best packages for each URI, according to the following rules:
139137
// - If there are any valid real packages, choose them.
140138
// - Else, choose the first valid command-line-argument package, if it exists.
141139
//
142-
// TODO(rfindley): it might be better to track all IDs here, and exclude
140+
// TODO(rfindley): it might be better to track all packages here, and exclude
143141
// them later when type checking, but this is the existing behavior.
144-
for i, id := range ids {
142+
for i, pkg := range pkgs {
145143
// If we've seen *anything* prior to command-line arguments package, take
146-
// it. Note that ids[0] may itself be command-line-arguments.
147-
if i > 0 && IsCommandLineArguments(id) {
148-
uriIDs[uri] = ids[:i]
144+
// it. Note that pkgs[0] may itself be command-line-arguments.
145+
if i > 0 && IsCommandLineArguments(pkg.ID) {
146+
uriPkgs[uri] = pkgs[:i]
149147
break
150148
}
151149
}
@@ -167,10 +165,10 @@ func newGraph(pkgs map[PackageID]*Package) *Graph {
167165
}
168166

169167
return &Graph{
170-
Packages: pkgs,
171-
ImportedBy: importedBy,
172-
ByPackagePath: byPackagePath,
173-
IDs: uriIDs,
168+
Packages: pkgs,
169+
ImportedBy: importedBy,
170+
ForPackagePath: byPackagePath,
171+
ForFile: uriPkgs,
174172
}
175173
}
176174

@@ -179,18 +177,22 @@ func newGraph(pkgs map[PackageID]*Package) *Graph {
179177
// transitively imports one of them, keyed by ID, including all the initial packages.
180178
func (g *Graph) ReverseReflexiveTransitiveClosure(ids ...PackageID) map[PackageID]*Package {
181179
seen := make(map[PackageID]*Package)
182-
var visitAll func([]PackageID)
183-
visitAll = func(ids []PackageID) {
184-
for _, id := range ids {
185-
if seen[id] == nil {
186-
if mp := g.Packages[id]; mp != nil {
187-
seen[id] = mp
188-
visitAll(g.ImportedBy[id])
189-
}
180+
var visitAll func([]*Package)
181+
visitAll = func(pkgs []*Package) {
182+
for _, pkg := range pkgs {
183+
if seen[pkg.ID] == nil {
184+
seen[pkg.ID] = pkg
185+
visitAll(g.ImportedBy[pkg.ID])
190186
}
191187
}
192188
}
193-
visitAll(ids)
189+
var initial []*Package
190+
for _, id := range ids {
191+
if pkg := g.Packages[id]; pkg != nil {
192+
initial = append(initial, pkg)
193+
}
194+
}
195+
visitAll(initial)
194196
return seen
195197
}
196198

gopls/internal/cache/session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,8 @@ func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Sn
436436
//
437437
// Any view can load a command-line-arguments package; aggregate those into
438438
// views[0] below.
439-
for _, id := range g.IDs[uri] {
440-
if !metadata.IsCommandLineArguments(id) || g.Packages[id].Standalone {
439+
for _, pkg := range g.ForFile[uri] {
440+
if !metadata.IsCommandLineArguments(pkg.ID) || pkg.Standalone {
441441
return snapshot, release, nil
442442
}
443443
}

gopls/internal/cache/snapshot.go

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -690,11 +690,11 @@ func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI
690690
s.mu.Lock()
691691

692692
// Start with the set of package associations derived from the last load.
693-
ids := s.meta.IDs[uri]
693+
pkgs := s.meta.ForFile[uri]
694694

695695
shouldLoad := false // whether any packages containing uri are marked 'shouldLoad'
696-
for _, id := range ids {
697-
if pkgs, _ := s.shouldLoad.Get(id); len(pkgs) > 0 {
696+
for _, pkg := range pkgs {
697+
if p, _ := s.shouldLoad.Get(pkg.ID); len(p) > 0 {
698698
shouldLoad = true
699699
}
700700
}
@@ -708,7 +708,7 @@ func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI
708708
// - uri is not contained in any valid packages
709709
// - ...or one of the packages containing uri is marked 'shouldLoad'
710710
// - ...but uri is not unloadable
711-
if (shouldLoad || len(ids) == 0) && !unloadable {
711+
if (shouldLoad || len(pkgs) == 0) && !unloadable {
712712
scope := fileLoadScope(uri)
713713
err := s.load(ctx, NoNetwork, scope)
714714

@@ -741,18 +741,14 @@ func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI
741741
// Retrieve the metadata.
742742
s.mu.Lock()
743743
defer s.mu.Unlock()
744-
ids = s.meta.IDs[uri]
745-
metas := make([]*metadata.Package, len(ids))
746-
for i, id := range ids {
747-
metas[i] = s.meta.Packages[id]
748-
if metas[i] == nil {
749-
panic("nil metadata")
750-
}
751-
}
744+
// TODO(rfindley): is there any reason not to make the sorting below the
745+
// canonical sorting, so that we don't need to mutate this slice?
746+
metas := slices.Clone(s.meta.ForFile[uri])
747+
752748
// Metadata is only ever added by loading,
753749
// so if we get here and still have
754-
// no IDs, uri is unloadable.
755-
if !unloadable && len(ids) == 0 {
750+
// no packages, uri is unloadable.
751+
if !unloadable && len(metas) == 0 {
756752
s.unloadableFiles.Add(uri)
757753
}
758754

@@ -796,10 +792,8 @@ func (s *Snapshot) ReverseDependencies(ctx context.Context, id PackageID, transi
796792
} else {
797793
// direct reverse dependencies
798794
rdeps = make(map[PackageID]*metadata.Package)
799-
for _, rdepID := range meta.ImportedBy[id] {
800-
if rdep := meta.Packages[rdepID]; rdep != nil {
801-
rdeps[rdepID] = rdep
802-
}
795+
for _, rdep := range meta.ImportedBy[id] {
796+
rdeps[rdep.ID] = rdep
803797
}
804798
}
805799

@@ -1061,9 +1055,8 @@ func (s *Snapshot) clearShouldLoad(scopes ...loadScope) {
10611055
}
10621056
case fileLoadScope:
10631057
uri := protocol.DocumentURI(scope)
1064-
ids := s.meta.IDs[uri]
1065-
for _, id := range ids {
1066-
s.shouldLoad.Delete(id)
1058+
for _, pkg := range s.meta.ForFile[uri] {
1059+
s.shouldLoad.Delete(pkg.ID)
10671060
}
10681061
}
10691062
}
@@ -1706,7 +1699,7 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f
17061699
anyPkgFileChanged = anyPkgFileChanged || pkgFileChanged
17071700

17081701
// Mark all of the package IDs containing the given file.
1709-
filePackageIDs := invalidatedPackageIDs(uri, s.meta.IDs, pkgFileChanged)
1702+
filePackageIDs := invalidatedPackageIDs(uri, s.meta.ForFile, pkgFileChanged)
17101703
for id := range filePackageIDs {
17111704
directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false'
17121705
}
@@ -1807,8 +1800,8 @@ func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done f
18071800
return
18081801
}
18091802
idsToInvalidate[id] = newInvalidateMetadata
1810-
for _, rid := range s.meta.ImportedBy[id] {
1811-
addRevDeps(rid, invalidateMetadata)
1803+
for _, rdep := range s.meta.ImportedBy[id] {
1804+
addRevDeps(rdep.ID, invalidateMetadata)
18121805
}
18131806
}
18141807
for id, invalidateMetadata := range directIDs {
@@ -1942,12 +1935,12 @@ func deleteMostRelevantModFile(m *persistent.Map[protocol.DocumentURI, *memoize.
19421935
// If packageFileChanged is set, the file is either a new file, or has a new
19431936
// package name. In this case, all known packages in the directory will be
19441937
// invalidated.
1945-
func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.DocumentURI][]PackageID, packageFileChanged bool) map[PackageID]struct{} {
1938+
func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.DocumentURI][]*metadata.Package, packageFileChanged bool) map[PackageID]struct{} {
19461939
invalidated := make(map[PackageID]struct{})
19471940

19481941
// At a minimum, we invalidate packages known to contain uri.
1949-
for _, id := range known[uri] {
1950-
invalidated[id] = struct{}{}
1942+
for _, pkg := range known[uri] {
1943+
invalidated[pkg.ID] = struct{}{}
19511944
}
19521945

19531946
// If the file didn't move to a new package, we should only invalidate the
@@ -1980,15 +1973,15 @@ func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.Document
19801973
fi, err := getInfo(dir)
19811974
if err == nil {
19821975
// Aggregate all possibly relevant package IDs.
1983-
for knownURI, ids := range known {
1976+
for knownURI, pkgs := range known {
19841977
knownDir := knownURI.DirPath()
19851978
knownFI, err := getInfo(knownDir)
19861979
if err != nil {
19871980
continue
19881981
}
19891982
if os.SameFile(fi, knownFI) {
1990-
for _, id := range ids {
1991-
invalidated[id] = struct{}{}
1983+
for _, pkg := range pkgs {
1984+
invalidated[pkg.ID] = struct{}{}
19921985
}
19931986
}
19941987
}

gopls/internal/debug/serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ var MetadataTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
921921
922922
<h3 id='hdr-Files'>Files</h3>
923923
<ul>
924-
{{range $uri, $ids := .IDs}}<li>{{$uri}} →{{range $ids}} <a href='#{{.}}'>{{.}}</a>{{end}}</li>{{end}}
924+
{{range $uri, $pkgs := .ForFile}}<li>{{$uri}} →{{range $pkgs}} <a href='#{{.ID}}'>{{.ID}}</a>{{end}}</li>{{end}}
925925
</ul>
926926
927927
{{end}}

gopls/internal/golang/completion/unimported.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ func (c *completer) findPackageIDs(pkgname metadata.PackageName) (wsIDs, thisPkg
7979
continue
8080
}
8181
imports := g.ImportedBy[pid]
82-
if slices.Contains(imports, c.pkg.Metadata().ID) {
82+
// Metadata is not canonical: it may be held onto by a package. Therefore,
83+
// we must compare by ID.
84+
thisPkg := func(mp *metadata.Package) bool { return mp.ID == c.pkg.Metadata().ID }
85+
if slices.ContainsFunc(imports, thisPkg) {
8386
thisPkgIDs = append(thisPkgIDs, pid)
8487
} else {
8588
wsIDs = append(wsIDs, pid)

gopls/internal/mcp/outline.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (h *handler) outlineHandler(ctx context.Context, _ *mcp.ServerSession, para
4141
return nil, err
4242
}
4343

44-
var toSummarize []metadata.PackageID
44+
var toSummarize []*metadata.Package
4545
for _, imp := range params.Arguments.PackagePaths {
4646
pkgPath := metadata.PackagePath(imp)
4747
if len(imp) > 0 && imp[0] == '"' {
@@ -51,18 +51,17 @@ func (h *handler) outlineHandler(ctx context.Context, _ *mcp.ServerSession, para
5151
}
5252
pkgPath = metadata.PackagePath(unquoted)
5353
}
54-
if mps := md.ByPackagePath[pkgPath]; len(mps) > 0 {
55-
toSummarize = append(toSummarize, mps[0].ID) // first is best
54+
if mps := md.ForPackagePath[pkgPath]; len(mps) > 0 {
55+
toSummarize = append(toSummarize, mps[0]) // first is best
5656
}
5757
}
5858

5959
var content []*mcp.Content
60-
for _, id := range toSummarize {
61-
md := snapshot.Metadata(id)
60+
for _, mp := range toSummarize {
6261
if md == nil {
6362
continue // ignore error
6463
}
65-
if summary := summarizePackage(ctx, snapshot, md); summary != "" {
64+
if summary := summarizePackage(ctx, snapshot, mp); summary != "" {
6665
content = append(content, mcp.NewTextContent(summary))
6766
}
6867
}

gopls/internal/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func (s *server) initWeb() (*web, error) {
356356

357357
// Find package by path.
358358
pkgPath := metadata.PackagePath(req.URL.Path)
359-
mps := snapshot.MetadataGraph().ByPackagePath[pkgPath]
359+
mps := snapshot.MetadataGraph().ForPackagePath[pkgPath]
360360
if len(mps) == 0 {
361361
// TODO(adonovan): what should we do for external test packages?
362362
http.Error(w, "package not found", http.StatusNotFound)

0 commit comments

Comments
 (0)