1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package objectpath_test
6
7import (
8	"bytes"
9	"go/ast"
10	"go/importer"
11	"go/parser"
12	"go/token"
13	"go/types"
14	"strings"
15	"testing"
16
17	"golang.org/x/tools/go/buildutil"
18	"golang.org/x/tools/go/gcexportdata"
19	"golang.org/x/tools/go/loader"
20	"golang.org/x/tools/go/types/objectpath"
21)
22
23func TestPaths(t *testing.T) {
24	pkgs := map[string]map[string]string{
25		"b": {"b.go": `
26package b
27
28import "a"
29
30const C = a.Int(0)
31
32func F(a, b, c int, d a.T)
33
34type T struct{ A int; b int; a.T }
35
36func (T) M() *interface{ f() }
37
38type U T
39
40type A = struct{ x int }
41
42var V []*a.T
43
44type M map[struct{x int}]struct{y int}
45
46func unexportedFunc()
47type unexportedType struct{}
48`},
49		"a": {"a.go": `
50package a
51
52type Int int
53
54type T struct{x, y int}
55
56`},
57	}
58	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
59	conf.Import("a")
60	conf.Import("b")
61	prog, err := conf.Load()
62	if err != nil {
63		t.Fatal(err)
64	}
65	a := prog.Imported["a"].Pkg
66	b := prog.Imported["b"].Pkg
67
68	// We test objectpath by enumerating a set of paths
69	// and ensuring that Path(pkg, Object(pkg, path)) == path.
70	//
71	// It might seem more natural to invert the test:
72	// identify a set of objects and for each one,
73	// ensure that Object(pkg, Path(pkg, obj)) == obj.
74	// However, for most interesting test cases there is no
75	// easy way to identify the object short of applying
76	// a series of destructuring operations to pkg---which
77	// is essentially what objectpath.Object does.
78	// (We do a little of that when testing bad paths, below.)
79	//
80	// The downside is that the test depends on the path encoding.
81	// The upside is that the test exercises the encoding.
82
83	// good paths
84	for _, test := range []struct {
85		pkg     *types.Package
86		path    objectpath.Path
87		wantobj string
88	}{
89		{b, "C", "const b.C a.Int"},
90		{b, "F", "func b.F(a int, b int, c int, d a.T)"},
91		{b, "F.PA0", "var a int"},
92		{b, "F.PA1", "var b int"},
93		{b, "F.PA2", "var c int"},
94		{b, "F.PA3", "var d a.T"},
95		{b, "T", "type b.T struct{A int; b int; a.T}"},
96		{b, "T.O", "type b.T struct{A int; b int; a.T}"},
97		{b, "T.UF0", "field A int"},
98		{b, "T.UF1", "field b int"},
99		{b, "T.UF2", "field T a.T"},
100		{b, "U.UF2", "field T a.T"}, // U.U... are aliases for T.U...
101		{b, "A", "type b.A = struct{x int}"},
102		{b, "A.F0", "field x int"},
103		{b, "V", "var b.V []*a.T"},
104		{b, "M", "type b.M map[struct{x int}]struct{y int}"},
105		{b, "M.UKF0", "field x int"},
106		{b, "M.UEF0", "field y int"},
107		{b, "T.M0", "func (b.T).M() *interface{f()}"}, // concrete method
108		{b, "T.M0.RA0", "var  *interface{f()}"},       // parameter
109		{b, "T.M0.RA0.EM0", "func (interface).f()"},   // interface method
110		{b, "unexportedType", "type b.unexportedType struct{}"},
111		{a, "T", "type a.T struct{x int; y int}"},
112		{a, "T.UF0", "field x int"},
113	} {
114		// check path -> object
115		obj, err := objectpath.Object(test.pkg, test.path)
116		if err != nil {
117			t.Errorf("Object(%s, %q) failed: %v",
118				test.pkg.Path(), test.path, err)
119			continue
120		}
121		if obj.String() != test.wantobj {
122			t.Errorf("Object(%s, %q) = %v, want %s",
123				test.pkg.Path(), test.path, obj, test.wantobj)
124			continue
125		}
126		if obj.Pkg() != test.pkg {
127			t.Errorf("Object(%s, %q) = %v, which belongs to package %s",
128				test.pkg.Path(), test.path, obj, obj.Pkg().Path())
129			continue
130		}
131
132		// check object -> path
133		path2, err := objectpath.For(obj)
134		if err != nil {
135			t.Errorf("For(%v) failed: %v, want %q", obj, err, test.path)
136			continue
137		}
138		// We do not require that test.path == path2. Aliases are legal.
139		// But we do require that Object(path2) finds the same object.
140		obj2, err := objectpath.Object(test.pkg, path2)
141		if err != nil {
142			t.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)",
143				test.pkg.Path(), path2, err, test.path)
144			continue
145		}
146		if obj2 != obj {
147			t.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)",
148				test.pkg.Path(), obj2, obj, test.path, path2)
149			continue
150		}
151	}
152
153	// bad paths (all relative to package b)
154	for _, test := range []struct {
155		pkg     *types.Package
156		path    objectpath.Path
157		wantErr string
158	}{
159		{b, "", "empty path"},
160		{b, "missing", `package b does not contain "missing"`},
161		{b, "F.U", "invalid path: ends with 'U', want [AFMO]"},
162		{b, "F.PA3.O", "path denotes type a.T struct{x int; y int}, which belongs to a different package"},
163		{b, "F.PA!", `invalid path: bad numeric operand "" for code 'A'`},
164		{b, "F.PA3.UF0", "path denotes field x int, which belongs to a different package"},
165		{b, "F.PA3.UF5", "field index 5 out of range [0-2)"},
166		{b, "V.EE", "invalid path: ends with 'E', want [AFMO]"},
167		{b, "F..O", "invalid path: unexpected '.' in type context"},
168		{b, "T.OO", "invalid path: code 'O' in object context"},
169		{b, "T.EO", "cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
170		{b, "A.O", "cannot apply 'O' to struct{x int} (got struct{x int}, want named)"},
171		{b, "A.UF0", "cannot apply 'U' to struct{x int} (got struct{x int}, want named)"},
172		{b, "M.UPO", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
173		{b, "C.O", "path denotes type a.Int int, which belongs to a different package"},
174	} {
175		obj, err := objectpath.Object(test.pkg, test.path)
176		if err == nil {
177			t.Errorf("Object(%s, %q) = %s, want error",
178				test.pkg.Path(), test.path, obj)
179			continue
180		}
181		if err.Error() != test.wantErr {
182			t.Errorf("Object(%s, %q) error was %q, want %q",
183				test.pkg.Path(), test.path, err, test.wantErr)
184			continue
185		}
186	}
187
188	// bad objects
189	bInfo := prog.Imported["b"]
190	for _, test := range []struct {
191		obj     types.Object
192		wantErr string
193	}{
194		{types.Universe.Lookup("nil"), "predeclared nil has no path"},
195		{types.Universe.Lookup("len"), "predeclared builtin len has no path"},
196		{types.Universe.Lookup("int"), "predeclared type int has no path"},
197		{bInfo.Info.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
198		{b.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
199	} {
200		path, err := objectpath.For(test.obj)
201		if err == nil {
202			t.Errorf("Object(%s) = %q, want error", test.obj, path)
203			continue
204		}
205		if err.Error() != test.wantErr {
206			t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
207			continue
208		}
209	}
210}
211
212// TestSourceAndExportData uses objectpath to compute a correspondence
213// of objects between two versions of the same package, one loaded from
214// source, the other from export data.
215func TestSourceAndExportData(t *testing.T) {
216	const src = `
217package p
218
219type I int
220
221func (I) F() *struct{ X, Y int } {
222	return nil
223}
224
225type Foo interface {
226	Method() (string, func(int) struct{ X int })
227}
228
229var X chan struct{ Z int }
230var Z map[string]struct{ A int }
231`
232
233	// Parse source file and type-check it as a package, "src".
234	fset := token.NewFileSet()
235	f, err := parser.ParseFile(fset, "src.go", src, 0)
236	if err != nil {
237		t.Fatal(err)
238	}
239	conf := types.Config{Importer: importer.For("source", nil)}
240	info := &types.Info{
241		Defs: make(map[*ast.Ident]types.Object),
242	}
243	srcpkg, err := conf.Check("src/p", fset, []*ast.File{f}, info)
244	if err != nil {
245		t.Fatal(err)
246	}
247
248	// Export binary export data then reload it as a new package, "bin".
249	var buf bytes.Buffer
250	if err := gcexportdata.Write(&buf, fset, srcpkg); err != nil {
251		t.Fatal(err)
252	}
253
254	imports := make(map[string]*types.Package)
255	binpkg, err := gcexportdata.Read(&buf, fset, imports, "bin/p")
256	if err != nil {
257		t.Fatal(err)
258	}
259
260	// Now find the correspondences between them.
261	for _, srcobj := range info.Defs {
262		if srcobj == nil {
263			continue // e.g. package declaration
264		}
265		if _, ok := srcobj.(*types.PkgName); ok {
266			continue // PkgName has no objectpath
267		}
268
269		path, err := objectpath.For(srcobj)
270		if err != nil {
271			t.Errorf("For(%v): %v", srcobj, err)
272			continue
273		}
274		binobj, err := objectpath.Object(binpkg, path)
275		if err != nil {
276			t.Errorf("Object(%s, %q): %v", binpkg.Path(), path, err)
277			continue
278		}
279
280		// Check the object strings match.
281		// (We can't check that types are identical because the
282		// objects belong to different type-checker realms.)
283		srcstr := objectString(srcobj)
284		binstr := objectString(binobj)
285		if srcstr != binstr {
286			t.Errorf("ObjectStrings do not match: Object(For(%q)) = %s, want %s",
287				path, srcstr, binstr)
288			continue
289		}
290	}
291}
292
293func objectString(obj types.Object) string {
294	s := types.ObjectString(obj, (*types.Package).Name)
295
296	// The printing of interface methods changed in go1.11.
297	// This work-around makes the specific test pass with earlier versions.
298	s = strings.Replace(s, "func (interface).Method", "func (p.Foo).Method", -1)
299
300	return s
301}
302