1// Copyright 2016 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 gcimporter_test 6 7import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "go/constant" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "reflect" 16 "runtime" 17 "strings" 18 "testing" 19 20 "golang.org/x/tools/go/buildutil" 21 "golang.org/x/tools/go/internal/gcimporter" 22 "golang.org/x/tools/go/loader" 23) 24 25func TestBExportData_stdlib(t *testing.T) { 26 if runtime.Compiler == "gccgo" { 27 t.Skip("gccgo standard library is inaccessible") 28 } 29 if runtime.GOOS == "android" { 30 t.Skipf("incomplete std lib on %s", runtime.GOOS) 31 } 32 33 // Load, parse and type-check the program. 34 ctxt := build.Default // copy 35 ctxt.GOPATH = "" // disable GOPATH 36 conf := loader.Config{ 37 Build: &ctxt, 38 AllowErrors: true, 39 } 40 for _, path := range buildutil.AllPackages(conf.Build) { 41 conf.Import(path) 42 } 43 44 // Create a package containing type and value errors to ensure 45 // they are properly encoded/decoded. 46 f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors 47const UnknownValue = "" + 0 48type UnknownType undefined 49`) 50 if err != nil { 51 t.Fatal(err) 52 } 53 conf.CreateFromFiles("haserrors", f) 54 55 prog, err := conf.Load() 56 if err != nil { 57 t.Fatalf("Load failed: %v", err) 58 } 59 60 numPkgs := len(prog.AllPackages) 61 if want := 248; numPkgs < want { 62 t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) 63 } 64 65 for pkg, info := range prog.AllPackages { 66 if info.Files == nil { 67 continue // empty directory 68 } 69 exportdata, err := gcimporter.BExportData(conf.Fset, pkg) 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 imports := make(map[string]*types.Package) 75 fset2 := token.NewFileSet() 76 n, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path()) 77 if err != nil { 78 t.Errorf("BImportData(%s): %v", pkg.Path(), err) 79 continue 80 } 81 if n != len(exportdata) { 82 t.Errorf("BImportData(%s) decoded %d bytes, want %d", 83 pkg.Path(), n, len(exportdata)) 84 } 85 86 // Compare the packages' corresponding members. 87 for _, name := range pkg.Scope().Names() { 88 if !ast.IsExported(name) { 89 continue 90 } 91 obj1 := pkg.Scope().Lookup(name) 92 obj2 := pkg2.Scope().Lookup(name) 93 if obj2 == nil { 94 t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1) 95 continue 96 } 97 98 fl1 := fileLine(conf.Fset, obj1) 99 fl2 := fileLine(fset2, obj2) 100 if fl1 != fl2 { 101 t.Errorf("%s.%s: got posn %s, want %s", 102 pkg.Path(), name, fl2, fl1) 103 } 104 105 if err := equalObj(obj1, obj2); err != nil { 106 t.Errorf("%s.%s: %s\ngot: %s\nwant: %s", 107 pkg.Path(), name, err, obj2, obj1) 108 } 109 } 110 } 111} 112 113func fileLine(fset *token.FileSet, obj types.Object) string { 114 posn := fset.Position(obj.Pos()) 115 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 116} 117 118// equalObj reports how x and y differ. They are assumed to belong to 119// different universes so cannot be compared directly. 120func equalObj(x, y types.Object) error { 121 if reflect.TypeOf(x) != reflect.TypeOf(y) { 122 return fmt.Errorf("%T vs %T", x, y) 123 } 124 xt := x.Type() 125 yt := y.Type() 126 switch x.(type) { 127 case *types.Var, *types.Func: 128 // ok 129 case *types.Const: 130 xval := x.(*types.Const).Val() 131 yval := y.(*types.Const).Val() 132 // Use string comparison for floating-point values since rounding is permitted. 133 if constant.Compare(xval, token.NEQ, yval) && 134 !(xval.Kind() == constant.Float && xval.String() == yval.String()) { 135 return fmt.Errorf("unequal constants %s vs %s", xval, yval) 136 } 137 case *types.TypeName: 138 xt = xt.Underlying() 139 yt = yt.Underlying() 140 default: 141 return fmt.Errorf("unexpected %T", x) 142 } 143 return equalType(xt, yt) 144} 145 146func equalType(x, y types.Type) error { 147 if reflect.TypeOf(x) != reflect.TypeOf(y) { 148 return fmt.Errorf("unequal kinds: %T vs %T", x, y) 149 } 150 switch x := x.(type) { 151 case *types.Interface: 152 y := y.(*types.Interface) 153 // TODO(gri): enable separate emission of Embedded interfaces 154 // and ExplicitMethods then use this logic. 155 // if x.NumEmbeddeds() != y.NumEmbeddeds() { 156 // return fmt.Errorf("unequal number of embedded interfaces: %d vs %d", 157 // x.NumEmbeddeds(), y.NumEmbeddeds()) 158 // } 159 // for i := 0; i < x.NumEmbeddeds(); i++ { 160 // xi := x.Embedded(i) 161 // yi := y.Embedded(i) 162 // if xi.String() != yi.String() { 163 // return fmt.Errorf("mismatched %th embedded interface: %s vs %s", 164 // i, xi, yi) 165 // } 166 // } 167 // if x.NumExplicitMethods() != y.NumExplicitMethods() { 168 // return fmt.Errorf("unequal methods: %d vs %d", 169 // x.NumExplicitMethods(), y.NumExplicitMethods()) 170 // } 171 // for i := 0; i < x.NumExplicitMethods(); i++ { 172 // xm := x.ExplicitMethod(i) 173 // ym := y.ExplicitMethod(i) 174 // if xm.Name() != ym.Name() { 175 // return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym) 176 // } 177 // if err := equalType(xm.Type(), ym.Type()); err != nil { 178 // return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) 179 // } 180 // } 181 if x.NumMethods() != y.NumMethods() { 182 return fmt.Errorf("unequal methods: %d vs %d", 183 x.NumMethods(), y.NumMethods()) 184 } 185 for i := 0; i < x.NumMethods(); i++ { 186 xm := x.Method(i) 187 ym := y.Method(i) 188 if xm.Name() != ym.Name() { 189 return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym) 190 } 191 if err := equalType(xm.Type(), ym.Type()); err != nil { 192 return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) 193 } 194 } 195 case *types.Array: 196 y := y.(*types.Array) 197 if x.Len() != y.Len() { 198 return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len()) 199 } 200 if err := equalType(x.Elem(), y.Elem()); err != nil { 201 return fmt.Errorf("array elements: %s", err) 202 } 203 case *types.Basic: 204 y := y.(*types.Basic) 205 if x.Kind() != y.Kind() { 206 return fmt.Errorf("unequal basic types: %s vs %s", x, y) 207 } 208 case *types.Chan: 209 y := y.(*types.Chan) 210 if x.Dir() != y.Dir() { 211 return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir()) 212 } 213 if err := equalType(x.Elem(), y.Elem()); err != nil { 214 return fmt.Errorf("channel elements: %s", err) 215 } 216 case *types.Map: 217 y := y.(*types.Map) 218 if err := equalType(x.Key(), y.Key()); err != nil { 219 return fmt.Errorf("map keys: %s", err) 220 } 221 if err := equalType(x.Elem(), y.Elem()); err != nil { 222 return fmt.Errorf("map values: %s", err) 223 } 224 case *types.Named: 225 y := y.(*types.Named) 226 if x.String() != y.String() { 227 return fmt.Errorf("unequal named types: %s vs %s", x, y) 228 } 229 case *types.Pointer: 230 y := y.(*types.Pointer) 231 if err := equalType(x.Elem(), y.Elem()); err != nil { 232 return fmt.Errorf("pointer elements: %s", err) 233 } 234 case *types.Signature: 235 y := y.(*types.Signature) 236 if err := equalType(x.Params(), y.Params()); err != nil { 237 return fmt.Errorf("parameters: %s", err) 238 } 239 if err := equalType(x.Results(), y.Results()); err != nil { 240 return fmt.Errorf("results: %s", err) 241 } 242 if x.Variadic() != y.Variadic() { 243 return fmt.Errorf("unequal varidicity: %t vs %t", 244 x.Variadic(), y.Variadic()) 245 } 246 if (x.Recv() != nil) != (y.Recv() != nil) { 247 return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv()) 248 } 249 if x.Recv() != nil { 250 // TODO(adonovan): fix: this assertion fires for interface methods. 251 // The type of the receiver of an interface method is a named type 252 // if the Package was loaded from export data, or an unnamed (interface) 253 // type if the Package was produced by type-checking ASTs. 254 // if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil { 255 // return fmt.Errorf("receiver: %s", err) 256 // } 257 } 258 case *types.Slice: 259 y := y.(*types.Slice) 260 if err := equalType(x.Elem(), y.Elem()); err != nil { 261 return fmt.Errorf("slice elements: %s", err) 262 } 263 case *types.Struct: 264 y := y.(*types.Struct) 265 if x.NumFields() != y.NumFields() { 266 return fmt.Errorf("unequal struct fields: %d vs %d", 267 x.NumFields(), y.NumFields()) 268 } 269 for i := 0; i < x.NumFields(); i++ { 270 xf := x.Field(i) 271 yf := y.Field(i) 272 if xf.Name() != yf.Name() { 273 return fmt.Errorf("mismatched fields: %s vs %s", xf, yf) 274 } 275 if err := equalType(xf.Type(), yf.Type()); err != nil { 276 return fmt.Errorf("struct field %s: %s", xf.Name(), err) 277 } 278 if x.Tag(i) != y.Tag(i) { 279 return fmt.Errorf("struct field %s has unequal tags: %q vs %q", 280 xf.Name(), x.Tag(i), y.Tag(i)) 281 } 282 } 283 case *types.Tuple: 284 y := y.(*types.Tuple) 285 if x.Len() != y.Len() { 286 return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len()) 287 } 288 for i := 0; i < x.Len(); i++ { 289 if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil { 290 return fmt.Errorf("tuple element %d: %s", i, err) 291 } 292 } 293 } 294 return nil 295} 296 297// TestVeryLongFile tests the position of an import object declared in 298// a very long input file. Line numbers greater than maxlines are 299// reported as line 1, not garbage or token.NoPos. 300func TestVeryLongFile(t *testing.T) { 301 // parse and typecheck 302 longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" 303 fset1 := token.NewFileSet() 304 f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) 305 if err != nil { 306 t.Fatal(err) 307 } 308 var conf types.Config 309 pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) 310 if err != nil { 311 t.Fatal(err) 312 } 313 314 // export 315 exportdata, err := gcimporter.BExportData(fset1, pkg) 316 if err != nil { 317 t.Fatal(err) 318 } 319 320 // import 321 imports := make(map[string]*types.Package) 322 fset2 := token.NewFileSet() 323 _, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path()) 324 if err != nil { 325 t.Fatalf("BImportData(%s): %v", pkg.Path(), err) 326 } 327 328 // compare 329 posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) 330 posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) 331 if want := "foo.go:1:1"; posn2.String() != want { 332 t.Errorf("X position = %s, want %s (orig was %s)", 333 posn2, want, posn1) 334 } 335} 336 337const src = ` 338package p 339 340type ( 341 T0 = int32 342 T1 = struct{} 343 T2 = struct{ T1 } 344 Invalid = foo // foo is undeclared 345) 346` 347 348func checkPkg(t *testing.T, pkg *types.Package, label string) { 349 T1 := types.NewStruct(nil, nil) 350 T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil) 351 352 for _, test := range []struct { 353 name string 354 typ types.Type 355 }{ 356 {"T0", types.Typ[types.Int32]}, 357 {"T1", T1}, 358 {"T2", T2}, 359 {"Invalid", types.Typ[types.Invalid]}, 360 } { 361 obj := pkg.Scope().Lookup(test.name) 362 if obj == nil { 363 t.Errorf("%s: %s not found", label, test.name) 364 continue 365 } 366 tname, _ := obj.(*types.TypeName) 367 if tname == nil { 368 t.Errorf("%s: %v not a type name", label, obj) 369 continue 370 } 371 if !tname.IsAlias() { 372 t.Errorf("%s: %v: not marked as alias", label, tname) 373 continue 374 } 375 if got := tname.Type(); !types.Identical(got, test.typ) { 376 t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ) 377 } 378 } 379} 380 381func TestTypeAliases(t *testing.T) { 382 // parse and typecheck 383 fset1 := token.NewFileSet() 384 f, err := parser.ParseFile(fset1, "p.go", src, 0) 385 if err != nil { 386 t.Fatal(err) 387 } 388 var conf types.Config 389 pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil) 390 if err == nil { 391 // foo in undeclared in src; we should see an error 392 t.Fatal("invalid source type-checked without error") 393 } 394 if pkg1 == nil { 395 // despite incorrect src we should see a (partially) type-checked package 396 t.Fatal("nil package returned") 397 } 398 checkPkg(t, pkg1, "export") 399 400 // export 401 exportdata, err := gcimporter.BExportData(fset1, pkg1) 402 if err != nil { 403 t.Fatal(err) 404 } 405 406 // import 407 imports := make(map[string]*types.Package) 408 fset2 := token.NewFileSet() 409 _, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg1.Path()) 410 if err != nil { 411 t.Fatalf("BImportData(%s): %v", pkg1.Path(), err) 412 } 413 checkPkg(t, pkg2, "import") 414} 415