github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/types/objectpath/objectpath_test.go (about) 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 "github.com/powerman/golang-tools/go/buildutil" 19 "github.com/powerman/golang-tools/go/gcexportdata" 20 "github.com/powerman/golang-tools/go/loader" 21 "github.com/powerman/golang-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 }