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 <noreply+kokoro@google.com>
Reviewed-by: Tim King <taking@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/go/types/objectpath/objectpath.go b/go/types/objectpath/objectpath.go
index be8f5a8..e064a1a 100644
--- a/go/types/objectpath/objectpath.go
+++ b/go/types/objectpath/objectpath.go
@@ -113,6 +113,20 @@
opObj = 'O' // .Obj() (Named, TypeParam)
)
+// For is equivalent to new(Encoder).For(obj).
+//
+// It may be more efficient to reuse a single Encoder across several calls.
+func For(obj types.Object) (Path, error) {
+ return new(Encoder).For(obj)
+}
+
+// An Encoder amortizes the cost of encoding the paths of multiple objects.
+// The zero value of an Encoder is ready to use.
+type Encoder struct {
+ scopeNamesMemo map[*types.Scope][]string // memoization of Scope.Names()
+ namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
+}
+
// For returns the path to an object relative to its package,
// or an error if the object is not accessible from the package's Scope.
//
@@ -145,24 +159,7 @@
// .Type().Field(0) (field Var X)
//
// where p is the package (*types.Package) to which X belongs.
-func For(obj types.Object) (Path, error) {
- return newEncoderFor()(obj)
-}
-
-// An encoder amortizes the cost of encoding the paths of multiple objects.
-// Nonexported pending approval of proposal 58668.
-type encoder struct {
- scopeNamesMemo map[*types.Scope][]string // memoization of Scope.Names()
- namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
-}
-
-// Exposed to gopls via golang.org/x/tools/internal/typesinternal
-// pending approval of proposal 58668.
-//
-//go:linkname newEncoderFor
-func newEncoderFor() func(types.Object) (Path, error) { return new(encoder).For }
-
-func (enc *encoder) For(obj types.Object) (Path, error) {
+func (enc *Encoder) For(obj types.Object) (Path, error) {
pkg := obj.Pkg()
// This table lists the cases of interest.
@@ -341,7 +338,7 @@
// This function is just an optimization that avoids the general scope walking
// approach. You are expected to fall back to the general approach if this
// function fails.
-func (enc *encoder) concreteMethod(meth *types.Func) (Path, bool) {
+func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
// Concrete methods can only be declared on package-scoped named types. For
// that reason we can skip the expensive walk over the package scope: the
// path will always be package -> named type -> method. We can trivially get
@@ -730,23 +727,8 @@
return methods
}
-// scopeNames is a memoization of scope.Names. Callers must not modify the result.
-func (enc *encoder) scopeNames(scope *types.Scope) []string {
- m := enc.scopeNamesMemo
- if m == nil {
- m = make(map[*types.Scope][]string)
- enc.scopeNamesMemo = m
- }
- names, ok := m[scope]
- if !ok {
- names = scope.Names() // allocates and sorts
- m[scope] = names
- }
- return names
-}
-
// namedMethods is a memoization of the namedMethods function. Callers must not modify the result.
-func (enc *encoder) namedMethods(named *types.Named) []*types.Func {
+func (enc *Encoder) namedMethods(named *types.Named) []*types.Func {
m := enc.namedMethodsMemo
if m == nil {
m = make(map[*types.Named][]*types.Func)
@@ -758,5 +740,19 @@
m[named] = methods
}
return methods
+}
+// scopeNames is a memoization of scope.Names. Callers must not modify the result.
+func (enc *Encoder) scopeNames(scope *types.Scope) []string {
+ m := enc.scopeNamesMemo
+ if m == nil {
+ m = make(map[*types.Scope][]string)
+ enc.scopeNamesMemo = m
+ }
+ names, ok := m[scope]
+ if !ok {
+ names = scope.Names() // allocates and sorts
+ m[scope] = names
+ }
+ return names
}
diff --git a/gopls/internal/lsp/source/methodsets/methodsets.go b/gopls/internal/lsp/source/methodsets/methodsets.go
index f8963b9..dac369b 100644
--- a/gopls/internal/lsp/source/methodsets/methodsets.go
+++ b/gopls/internal/lsp/source/methodsets/methodsets.go
@@ -57,7 +57,6 @@
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/gopls/internal/lsp/safetoken"
"golang.org/x/tools/internal/typeparams"
- "golang.org/x/tools/internal/typesinternal"
)
// An Index records the non-empty method sets of all package-level
@@ -232,7 +231,7 @@
return gobPosition{b.string(posn.Filename), posn.Offset, len(obj.Name())}
}
- objectpathFor := typesinternal.NewObjectpathFunc()
+ objectpathFor := new(objectpath.Encoder).For
// setindexInfo sets the (Posn, PkgPath, ObjectPath) fields for each method declaration.
setIndexInfo := func(m *gobMethod, method *types.Func) {
diff --git a/gopls/internal/lsp/source/xrefs/xrefs.go b/gopls/internal/lsp/source/xrefs/xrefs.go
index eda6964..6231f88 100644
--- a/gopls/internal/lsp/source/xrefs/xrefs.go
+++ b/gopls/internal/lsp/source/xrefs/xrefs.go
@@ -19,7 +19,6 @@
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
- "golang.org/x/tools/internal/typesinternal"
)
// Index constructs a serializable index of outbound cross-references
@@ -42,7 +41,7 @@
return objects
}
- objectpathFor := typesinternal.NewObjectpathFunc()
+ objectpathFor := new(objectpath.Encoder).For
for fileIndex, pgf := range files {