Skip to content

Commit ee04797

Browse files
committed
go/types/objectpath: canonical order for methods
The method ordering in types can depend on the parse order for types. In order to make objectpath robust against this, simply encode all methods with respect to a canonical lexicographical ordering. Fixes golang/go#44195 Change-Id: I4177d9b4e094452f71d4db1813a5a36b54d0d70a Reviewed-on: https://go-review.googlesource.com/c/tools/+/354433 Run-TryBot: Tim King <[email protected]> Run-TryBot: Zvonimir Pavlinovic <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Findley <[email protected]> Trust: Zvonimir Pavlinovic <[email protected]>
1 parent c5188f2 commit ee04797

File tree

2 files changed

+114
-11
lines changed

2 files changed

+114
-11
lines changed

go/types/objectpath/objectpath.go

+38-11
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ package objectpath
2323

2424
import (
2525
"fmt"
26+
"go/types"
27+
"sort"
2628
"strconv"
2729
"strings"
2830

29-
"go/types"
30-
3131
"golang.org/x/tools/internal/typeparams"
3232
)
3333

@@ -67,6 +67,8 @@ type Path string
6767
// which is encoded as a string of decimal digits.
6868
// These indices are stable across different representations
6969
// of the same package, even source and export data.
70+
// The indices used are implementation specific and may not correspond to
71+
// the argument to the go/types function.
7072
//
7173
// In the example below,
7274
//
@@ -287,8 +289,12 @@ func For(obj types.Object) (Path, error) {
287289
// Inspect declared methods of defined types.
288290
if T, ok := o.Type().(*types.Named); ok {
289291
path = append(path, opType)
290-
for i := 0; i < T.NumMethods(); i++ {
291-
m := T.Method(i)
292+
// Note that method index here is always with respect
293+
// to canonical ordering of methods, regardless of how
294+
// they appear in the underlying type.
295+
canonical := canonicalize(T)
296+
for i := 0; i < len(canonical); i++ {
297+
m := canonical[i]
292298
path2 := appendOpArg(path, opMethod, i)
293299
if m == obj {
294300
return Path(path2), nil // found declared method
@@ -421,11 +427,6 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
421427
type hasElem interface {
422428
Elem() types.Type
423429
}
424-
// abstraction of *types.{Interface,Named}
425-
type hasMethods interface {
426-
Method(int) *types.Func
427-
NumMethods() int
428-
}
429430
// abstraction of *types.{Named,Signature}
430431
type hasTypeParams interface {
431432
TypeParams() *typeparams.TypeParamList
@@ -563,10 +564,11 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
563564
if !ok {
564565
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
565566
}
566-
if n := hasMethods.NumMethods(); index >= n {
567+
canonical := canonicalize(hasMethods)
568+
if n := len(canonical); index >= n {
567569
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, n)
568570
}
569-
obj = hasMethods.Method(index)
571+
obj = canonical[index]
570572
t = nil
571573

572574
case opObj:
@@ -588,3 +590,28 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
588590

589591
return obj, nil // success
590592
}
593+
594+
// hasMethods is an abstraction of *types.{Interface,Named}. This is pulled up
595+
// because it is used by methodOrdering, which is in turn used by both encoding
596+
// and decoding.
597+
type hasMethods interface {
598+
Method(int) *types.Func
599+
NumMethods() int
600+
}
601+
602+
// canonicalize returns a canonical order for the methods in a hasMethod.
603+
func canonicalize(hm hasMethods) []*types.Func {
604+
count := hm.NumMethods()
605+
if count <= 0 {
606+
return nil
607+
}
608+
canon := make([]*types.Func, count)
609+
for i := 0; i < count; i++ {
610+
canon[i] = hm.Method(i)
611+
}
612+
less := func(i, j int) bool {
613+
return canon[i].Id() < canon[j].Id()
614+
}
615+
sort.Slice(canon, less)
616+
return canon
617+
}

go/types/objectpath/objectpath_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ type M map[struct{x int}]struct{y int}
4646
4747
func unexportedFunc()
4848
type unexportedType struct{}
49+
50+
type S struct{t struct{x int}}
51+
type R []struct{y int}
52+
type Q [2]struct{z int}
4953
`},
5054
"a": {"a.go": `
5155
package a
@@ -80,6 +84,9 @@ type T struct{x, y int}
8084
{"b", "T.M0.RA0", "var *interface{f()}", ""}, // parameter
8185
{"b", "T.M0.RA0.EM0", "func (interface).f()", ""}, // interface method
8286
{"b", "unexportedType", "type b.unexportedType struct{}", ""},
87+
{"b", "S.UF0.F0", "field x int", ""},
88+
{"b", "R.UEF0", "field y int", ""},
89+
{"b", "Q.UEF0", "field z int", ""},
8390
{"a", "T", "type a.T struct{x int; y int}", ""},
8491
{"a", "T.UF0", "field x int", ""},
8592

@@ -99,6 +106,13 @@ type T struct{x, y int}
99106
{"b", "A.UF0", "", "cannot apply 'U' to struct{x int} (got *types.Struct, want named)"},
100107
{"b", "M.UPO", "", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
101108
{"b", "C.O", "", "path denotes type a.Int int, which belongs to a different package"},
109+
{"b", "T.M9", "", "method index 9 out of range [0-1)"},
110+
{"b", "M.UF0", "", "cannot apply 'F' to map[struct{x int}]struct{y int} (got *types.Map, want struct)"},
111+
{"b", "V.KO", "", "cannot apply 'K' to []*a.T (got *types.Slice, want map)"},
112+
{"b", "V.A4", "", "cannot apply 'A' to []*a.T (got *types.Slice, want tuple)"},
113+
{"b", "V.RA0", "", "cannot apply 'R' to []*a.T (got *types.Slice, want signature)"},
114+
{"b", "F.PA4", "", "tuple index 4 out of range [0-4)"},
115+
{"b", "F.XO", "", "invalid path: unknown code 'X'"},
102116
}
103117
conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
104118
conf.Import("a")
@@ -310,3 +324,65 @@ func objectString(obj types.Object) string {
310324

311325
return s
312326
}
327+
328+
// TestOrdering uses objectpath over two Named types with the same method
329+
// names but in a different source order and checks that objectpath is the
330+
// same for methods with the same name.
331+
func TestOrdering(t *testing.T) {
332+
pkgs := map[string]map[string]string{
333+
"p": {"p.go": `
334+
package p
335+
336+
type T struct{ A int }
337+
338+
func (T) M() { }
339+
func (T) N() { }
340+
func (T) X() { }
341+
func (T) Y() { }
342+
`},
343+
"q": {"q.go": `
344+
package q
345+
346+
type T struct{ A int }
347+
348+
func (T) N() { }
349+
func (T) M() { }
350+
func (T) Y() { }
351+
func (T) X() { }
352+
`}}
353+
conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
354+
conf.Import("p")
355+
conf.Import("q")
356+
prog, err := conf.Load()
357+
if err != nil {
358+
t.Fatal(err)
359+
}
360+
p := prog.Imported["p"].Pkg
361+
q := prog.Imported["q"].Pkg
362+
363+
// From here, the objectpaths generated for p and q should be the
364+
// same. If they are not, then we are generating an ordering that is
365+
// dependent on the declaration of the types within the file.
366+
for _, test := range []struct {
367+
path objectpath.Path
368+
}{
369+
{"T.M0"},
370+
{"T.M1"},
371+
{"T.M2"},
372+
{"T.M3"},
373+
} {
374+
pobj, err := objectpath.Object(p, test.path)
375+
if err != nil {
376+
t.Errorf("Object(%s) failed in a1: %v", test.path, err)
377+
continue
378+
}
379+
qobj, err := objectpath.Object(q, test.path)
380+
if err != nil {
381+
t.Errorf("Object(%s) failed in a2: %v", test.path, err)
382+
continue
383+
}
384+
if pobj.Name() != pobj.Name() {
385+
t.Errorf("Objects(%s) not equal, got a1 = %v, a2 = %v", test.path, pobj.Name(), qobj.Name())
386+
}
387+
}
388+
}

0 commit comments

Comments
 (0)