@@ -31,6 +31,8 @@ import (
31
31
"strings"
32
32
33
33
"golang.org/x/tools/internal/typeparams"
34
+
35
+ _ "unsafe" // for go:linkname
34
36
)
35
37
36
38
// A Path is an opaque name that identifies a types.Object
@@ -111,7 +113,7 @@ const (
111
113
opObj = 'O' // .Obj() (Named, TypeParam)
112
114
)
113
115
114
- // The For function returns the path to an object relative to its package,
116
+ // For returns the path to an object relative to its package,
115
117
// or an error if the object is not accessible from the package's Scope.
116
118
//
117
119
// The For function guarantees to return a path only for the following objects:
@@ -144,6 +146,23 @@ const (
144
146
//
145
147
// where p is the package (*types.Package) to which X belongs.
146
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 ) {
147
166
pkg := obj .Pkg ()
148
167
149
168
// This table lists the cases of interest.
@@ -225,7 +244,7 @@ func For(obj types.Object) (Path, error) {
225
244
return "" , fmt .Errorf ("func is not a method: %v" , obj )
226
245
}
227
246
228
- if path , ok := concreteMethod (obj ); ok {
247
+ if path , ok := enc . concreteMethod (obj ); ok {
229
248
// Fast path for concrete methods that avoids looping over scope.
230
249
return path , nil
231
250
}
@@ -241,7 +260,7 @@ func For(obj types.Object) (Path, error) {
241
260
// the best paths because non-types may
242
261
// refer to types, but not the reverse.
243
262
empty := make ([]byte , 0 , 48 ) // initial space
244
- names := scope . Names ( )
263
+ names := enc . scopeNames ( scope )
245
264
for _ , name := range names {
246
265
o := scope .Lookup (name )
247
266
tname , ok := o .(* types.TypeName )
@@ -294,9 +313,7 @@ func For(obj types.Object) (Path, error) {
294
313
// Note that method index here is always with respect
295
314
// to canonical ordering of methods, regardless of how
296
315
// they appear in the underlying type.
297
- canonical := canonicalize (T )
298
- for i := 0 ; i < len (canonical ); i ++ {
299
- m := canonical [i ]
316
+ for i , m := range enc .namedMethods (T ) {
300
317
path2 := appendOpArg (path , opMethod , i )
301
318
if m == obj {
302
319
return Path (path2 ), nil // found declared method
@@ -324,7 +341,7 @@ func appendOpArg(path []byte, op byte, arg int) []byte {
324
341
// This function is just an optimization that avoids the general scope walking
325
342
// approach. You are expected to fall back to the general approach if this
326
343
// function fails.
327
- func concreteMethod (meth * types.Func ) (Path , bool ) {
344
+ func ( enc * encoder ) concreteMethod (meth * types.Func ) (Path , bool ) {
328
345
// Concrete methods can only be declared on package-scoped named types. For
329
346
// that reason we can skip the expensive walk over the package scope: the
330
347
// path will always be package -> named type -> method. We can trivially get
@@ -397,8 +414,7 @@ func concreteMethod(meth *types.Func) (Path, bool) {
397
414
path := make ([]byte , 0 , len (name )+ 8 )
398
415
path = append (path , name ... )
399
416
path = append (path , opType )
400
- canonical := canonicalize (named )
401
- for i , m := range canonical {
417
+ for i , m := range enc .namedMethods (named ) {
402
418
if m == meth {
403
419
path = appendOpArg (path , opMethod , i )
404
420
return Path (path ), true
@@ -663,15 +679,23 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
663
679
t = nil
664
680
665
681
case opMethod :
666
- hasMethods , ok := t .(hasMethods ) // Interface or Named
667
- if ! ok {
682
+ switch t := t .(type ) {
683
+ case * types.Interface :
684
+ if index >= t .NumMethods () {
685
+ return nil , fmt .Errorf ("method index %d out of range [0-%d)" , index , t .NumMethods ())
686
+ }
687
+ obj = t .Method (index ) // Id-ordered
688
+
689
+ case * types.Named :
690
+ methods := namedMethods (t ) // (unmemoized)
691
+ if index >= len (methods ) {
692
+ return nil , fmt .Errorf ("method index %d out of range [0-%d)" , index , len (methods ))
693
+ }
694
+ obj = methods [index ] // Id-ordered
695
+
696
+ default :
668
697
return nil , fmt .Errorf ("cannot apply %q to %s (got %T, want interface or named)" , code , t , t )
669
698
}
670
- canonical := canonicalize (hasMethods )
671
- if n := len (canonical ); index >= n {
672
- return nil , fmt .Errorf ("method index %d out of range [0-%d)" , index , n )
673
- }
674
- obj = canonical [index ]
675
699
t = nil
676
700
677
701
case opObj :
@@ -694,27 +718,45 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
694
718
return obj , nil // success
695
719
}
696
720
697
- // hasMethods is an abstraction of *types.{Interface,Named}. This is pulled up
698
- // because it is used by methodOrdering, which is in turn used by both encoding
699
- // and decoding.
700
- type hasMethods interface {
701
- Method (int ) * types.Func
702
- NumMethods () int
721
+ // namedMethods returns the methods of a Named type in ascending Id order.
722
+ func namedMethods (named * types.Named ) []* types.Func {
723
+ methods := make ([]* types.Func , named .NumMethods ())
724
+ for i := range methods {
725
+ methods [i ] = named .Method (i )
726
+ }
727
+ sort .Slice (methods , func (i , j int ) bool {
728
+ return methods [i ].Id () < methods [j ].Id ()
729
+ })
730
+ return methods
703
731
}
704
732
705
- // canonicalize returns a canonical order for the methods in a hasMethod.
706
- func canonicalize (hm hasMethods ) []* types.Func {
707
- count := hm .NumMethods ()
708
- if count <= 0 {
709
- return nil
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
710
744
}
711
- canon := make ([]* types.Func , count )
712
- for i := 0 ; i < count ; i ++ {
713
- canon [i ] = hm .Method (i )
745
+ return names
746
+ }
747
+
748
+ // namedMethods is a memoization of the namedMethods function. Callers must not modify the result.
749
+ func (enc * encoder ) namedMethods (named * types.Named ) []* types.Func {
750
+ m := enc .namedMethodsMemo
751
+ if m == nil {
752
+ m = make (map [* types.Named ][]* types.Func )
753
+ enc .namedMethodsMemo = m
714
754
}
715
- less := func (i , j int ) bool {
716
- return canon [i ].Id () < canon [j ].Id ()
755
+ methods , ok := m [named ]
756
+ if ! ok {
757
+ methods = namedMethods (named ) // allocates and sorts
758
+ m [named ] = methods
717
759
}
718
- sort . Slice ( canon , less )
719
- return canon
760
+ return methods
761
+
720
762
}
0 commit comments