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