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