| 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