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