Skip to content

Commit a5338c9

Browse files
committed
go/types/objectpath: add Encoder type, to amortize Scope.Names
This change adds a new Encoder type with For method, that is functionally equivalent to the old For method but avoids the surprising cost of repeated calls to Scope.Names, which allocates and sorts a slice. See golang/go#58668 Fixes golang/go#51017 Change-Id: I328e60c60f9bc312af253a0509aa029c41960d41 Reviewed-on: https://go-review.googlesource.com/c/tools/+/470678 gopls-CI: kokoro <[email protected]> Reviewed-by: Tim King <[email protected]> Run-TryBot: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 45ef829 commit a5338c9

File tree

3 files changed

+33
-39
lines changed

3 files changed

+33
-39
lines changed

go/types/objectpath/objectpath.go

+31-35
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ const (
113113
opObj = 'O' // .Obj() (Named, TypeParam)
114114
)
115115

116+
// For is equivalent to new(Encoder).For(obj).
117+
//
118+
// It may be more efficient to reuse a single Encoder across several calls.
119+
func For(obj types.Object) (Path, error) {
120+
return new(Encoder).For(obj)
121+
}
122+
123+
// An Encoder amortizes the cost of encoding the paths of multiple objects.
124+
// The zero value of an Encoder is ready to use.
125+
type Encoder struct {
126+
scopeNamesMemo map[*types.Scope][]string // memoization of Scope.Names()
127+
namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
128+
}
129+
116130
// For returns the path to an object relative to its package,
117131
// or an error if the object is not accessible from the package's Scope.
118132
//
@@ -145,24 +159,7 @@ const (
145159
// .Type().Field(0) (field Var X)
146160
//
147161
// where p is the package (*types.Package) to which X belongs.
148-
func For(obj types.Object) (Path, error) {
149-
return newEncoderFor()(obj)
150-
}
151-
152-
// An encoder amortizes the cost of encoding the paths of multiple objects.
153-
// Nonexported pending approval of proposal 58668.
154-
type encoder struct {
155-
scopeNamesMemo map[*types.Scope][]string // memoization of Scope.Names()
156-
namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
157-
}
158-
159-
// Exposed to gopls via golang.org/x/tools/internal/typesinternal
160-
// pending approval of proposal 58668.
161-
//
162-
//go:linkname newEncoderFor
163-
func newEncoderFor() func(types.Object) (Path, error) { return new(encoder).For }
164-
165-
func (enc *encoder) For(obj types.Object) (Path, error) {
162+
func (enc *Encoder) For(obj types.Object) (Path, error) {
166163
pkg := obj.Pkg()
167164

168165
// This table lists the cases of interest.
@@ -341,7 +338,7 @@ func appendOpArg(path []byte, op byte, arg int) []byte {
341338
// This function is just an optimization that avoids the general scope walking
342339
// approach. You are expected to fall back to the general approach if this
343340
// function fails.
344-
func (enc *encoder) concreteMethod(meth *types.Func) (Path, bool) {
341+
func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
345342
// Concrete methods can only be declared on package-scoped named types. For
346343
// that reason we can skip the expensive walk over the package scope: the
347344
// path will always be package -> named type -> method. We can trivially get
@@ -730,23 +727,8 @@ func namedMethods(named *types.Named) []*types.Func {
730727
return methods
731728
}
732729

733-
// scopeNames is a memoization of scope.Names. Callers must not modify the result.
734-
func (enc *encoder) scopeNames(scope *types.Scope) []string {
735-
m := enc.scopeNamesMemo
736-
if m == nil {
737-
m = make(map[*types.Scope][]string)
738-
enc.scopeNamesMemo = m
739-
}
740-
names, ok := m[scope]
741-
if !ok {
742-
names = scope.Names() // allocates and sorts
743-
m[scope] = names
744-
}
745-
return names
746-
}
747-
748730
// namedMethods is a memoization of the namedMethods function. Callers must not modify the result.
749-
func (enc *encoder) namedMethods(named *types.Named) []*types.Func {
731+
func (enc *Encoder) namedMethods(named *types.Named) []*types.Func {
750732
m := enc.namedMethodsMemo
751733
if m == nil {
752734
m = make(map[*types.Named][]*types.Func)
@@ -758,5 +740,19 @@ func (enc *encoder) namedMethods(named *types.Named) []*types.Func {
758740
m[named] = methods
759741
}
760742
return methods
743+
}
761744

745+
// scopeNames is a memoization of scope.Names. Callers must not modify the result.
746+
func (enc *Encoder) scopeNames(scope *types.Scope) []string {
747+
m := enc.scopeNamesMemo
748+
if m == nil {
749+
m = make(map[*types.Scope][]string)
750+
enc.scopeNamesMemo = m
751+
}
752+
names, ok := m[scope]
753+
if !ok {
754+
names = scope.Names() // allocates and sorts
755+
m[scope] = names
756+
}
757+
return names
762758
}

gopls/internal/lsp/source/methodsets/methodsets.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import (
5757
"golang.org/x/tools/go/types/objectpath"
5858
"golang.org/x/tools/gopls/internal/lsp/safetoken"
5959
"golang.org/x/tools/internal/typeparams"
60-
"golang.org/x/tools/internal/typesinternal"
6160
)
6261

6362
// An Index records the non-empty method sets of all package-level
@@ -232,7 +231,7 @@ func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index {
232231
return gobPosition{b.string(posn.Filename), posn.Offset, len(obj.Name())}
233232
}
234233

235-
objectpathFor := typesinternal.NewObjectpathFunc()
234+
objectpathFor := new(objectpath.Encoder).For
236235

237236
// setindexInfo sets the (Posn, PkgPath, ObjectPath) fields for each method declaration.
238237
setIndexInfo := func(m *gobMethod, method *types.Func) {

gopls/internal/lsp/source/xrefs/xrefs.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"golang.org/x/tools/go/types/objectpath"
2020
"golang.org/x/tools/gopls/internal/lsp/protocol"
2121
"golang.org/x/tools/gopls/internal/lsp/source"
22-
"golang.org/x/tools/internal/typesinternal"
2322
)
2423

2524
// Index constructs a serializable index of outbound cross-references
@@ -42,7 +41,7 @@ func Index(files []*source.ParsedGoFile, pkg *types.Package, info *types.Info) [
4241
return objects
4342
}
4443

45-
objectpathFor := typesinternal.NewObjectpathFunc()
44+
objectpathFor := new(objectpath.Encoder).For
4645

4746
for fileIndex, pgf := range files {
4847

0 commit comments

Comments
 (0)