github.com/akaros/go-akaros@v0.0.0-20181004170632-85005d477eab/src/cmd/api/goapi.go (about) 1 // Copyright 2011 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 // +build api_tool 6 7 // Binary api computes the exported API of a set of Go packages. 8 package main 9 10 import ( 11 "bufio" 12 "bytes" 13 "flag" 14 "fmt" 15 "go/ast" 16 "go/build" 17 "go/parser" 18 "go/token" 19 "io" 20 "io/ioutil" 21 "log" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "regexp" 26 "runtime" 27 "sort" 28 "strings" 29 30 "code.google.com/p/go.tools/go/types" 31 ) 32 33 // Flags 34 var ( 35 checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against") 36 allowNew = flag.Bool("allow_new", true, "allow API additions") 37 exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool") 38 nextFile = flag.String("next", "", "optional filename of tentative upcoming API features for the next release. This file can be lazily maintained. It only affects the delta warnings from the -c file printed on success.") 39 verbose = flag.Bool("v", false, "verbose debugging") 40 forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.") 41 ) 42 43 // contexts are the default contexts which are scanned, unless 44 // overridden by the -contexts flag. 45 var contexts = []*build.Context{ 46 {GOOS: "linux", GOARCH: "386", CgoEnabled: true}, 47 {GOOS: "linux", GOARCH: "386"}, 48 {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}, 49 {GOOS: "linux", GOARCH: "amd64"}, 50 {GOOS: "linux", GOARCH: "arm", CgoEnabled: true}, 51 {GOOS: "linux", GOARCH: "arm"}, 52 {GOOS: "akaros", GOARCH: "386", CgoEnabled: true}, 53 {GOOS: "akaros", GOARCH: "386"}, 54 {GOOS: "akaros", GOARCH: "amd64", CgoEnabled: true}, 55 {GOOS: "akaros", GOARCH: "amd64"}, 56 {GOOS: "darwin", GOARCH: "386", CgoEnabled: true}, 57 {GOOS: "darwin", GOARCH: "386"}, 58 {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true}, 59 {GOOS: "darwin", GOARCH: "amd64"}, 60 {GOOS: "windows", GOARCH: "amd64"}, 61 {GOOS: "windows", GOARCH: "386"}, 62 {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true}, 63 {GOOS: "freebsd", GOARCH: "386"}, 64 {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true}, 65 {GOOS: "freebsd", GOARCH: "amd64"}, 66 {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true}, 67 {GOOS: "freebsd", GOARCH: "arm"}, 68 {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true}, 69 {GOOS: "netbsd", GOARCH: "386"}, 70 {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true}, 71 {GOOS: "netbsd", GOARCH: "amd64"}, 72 {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true}, 73 {GOOS: "netbsd", GOARCH: "arm"}, 74 {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true}, 75 {GOOS: "openbsd", GOARCH: "386"}, 76 {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true}, 77 {GOOS: "openbsd", GOARCH: "amd64"}, 78 } 79 80 func contextName(c *build.Context) string { 81 s := c.GOOS + "-" + c.GOARCH 82 if c.CgoEnabled { 83 return s + "-cgo" 84 } 85 return s 86 } 87 88 func parseContext(c string) *build.Context { 89 parts := strings.Split(c, "-") 90 if len(parts) < 2 { 91 log.Fatalf("bad context: %q", c) 92 } 93 bc := &build.Context{ 94 GOOS: parts[0], 95 GOARCH: parts[1], 96 } 97 if len(parts) == 3 { 98 if parts[2] == "cgo" { 99 bc.CgoEnabled = true 100 } else { 101 log.Fatalf("bad context: %q", c) 102 } 103 } 104 return bc 105 } 106 107 func setContexts() { 108 contexts = []*build.Context{} 109 for _, c := range strings.Split(*forceCtx, ",") { 110 contexts = append(contexts, parseContext(c)) 111 } 112 } 113 114 var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`) 115 116 func main() { 117 flag.Parse() 118 119 if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") { 120 if *nextFile != "" { 121 fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile) 122 *nextFile = "" 123 } 124 } 125 126 if *forceCtx != "" { 127 setContexts() 128 } 129 for _, c := range contexts { 130 c.Compiler = build.Default.Compiler 131 } 132 133 var pkgNames []string 134 if flag.NArg() > 0 { 135 pkgNames = flag.Args() 136 } else { 137 stds, err := exec.Command("go", "list", "std").Output() 138 if err != nil { 139 log.Fatal(err) 140 } 141 for _, pkg := range strings.Fields(string(stds)) { 142 if !internalPkg.MatchString(pkg) { 143 pkgNames = append(pkgNames, pkg) 144 } 145 } 146 } 147 148 var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true 149 for _, context := range contexts { 150 w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src")) 151 152 for _, name := range pkgNames { 153 // - Package "unsafe" contains special signatures requiring 154 // extra care when printing them - ignore since it is not 155 // going to change w/o a language change. 156 // - We don't care about the API of commands. 157 if name != "unsafe" && !strings.HasPrefix(name, "cmd/") { 158 if name == "runtime/cgo" && !context.CgoEnabled { 159 // w.Import(name) will return nil 160 continue 161 } 162 w.export(w.Import(name)) 163 } 164 } 165 166 ctxName := contextName(context) 167 for _, f := range w.Features() { 168 if featureCtx[f] == nil { 169 featureCtx[f] = make(map[string]bool) 170 } 171 featureCtx[f][ctxName] = true 172 } 173 } 174 175 var features []string 176 for f, cmap := range featureCtx { 177 if len(cmap) == len(contexts) { 178 features = append(features, f) 179 continue 180 } 181 comma := strings.Index(f, ",") 182 for cname := range cmap { 183 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:]) 184 features = append(features, f2) 185 } 186 } 187 188 fail := false 189 defer func() { 190 if fail { 191 os.Exit(1) 192 } 193 }() 194 195 bw := bufio.NewWriter(os.Stdout) 196 defer bw.Flush() 197 198 if *checkFile == "" { 199 sort.Strings(features) 200 for _, f := range features { 201 fmt.Fprintln(bw, f) 202 } 203 return 204 } 205 206 var required []string 207 for _, file := range strings.Split(*checkFile, ",") { 208 required = append(required, fileFeatures(file)...) 209 } 210 optional := fileFeatures(*nextFile) 211 exception := fileFeatures(*exceptFile) 212 fail = !compareAPI(bw, features, required, optional, exception) 213 } 214 215 // export emits the exported package features. 216 func (w *Walker) export(pkg *types.Package) { 217 if *verbose { 218 log.Println(pkg) 219 } 220 pop := w.pushScope("pkg " + pkg.Path()) 221 w.current = pkg 222 scope := pkg.Scope() 223 for _, name := range scope.Names() { 224 if ast.IsExported(name) { 225 w.emitObj(scope.Lookup(name)) 226 } 227 } 228 pop() 229 } 230 231 func set(items []string) map[string]bool { 232 s := make(map[string]bool) 233 for _, v := range items { 234 s[v] = true 235 } 236 return s 237 } 238 239 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`) 240 241 func featureWithoutContext(f string) string { 242 if !strings.Contains(f, "(") { 243 return f 244 } 245 return spaceParensRx.ReplaceAllString(f, "") 246 } 247 248 func compareAPI(w io.Writer, features, required, optional, exception []string) (ok bool) { 249 ok = true 250 251 optionalSet := set(optional) 252 exceptionSet := set(exception) 253 featureSet := set(features) 254 255 sort.Strings(features) 256 sort.Strings(required) 257 258 take := func(sl *[]string) string { 259 s := (*sl)[0] 260 *sl = (*sl)[1:] 261 return s 262 } 263 264 for len(required) > 0 || len(features) > 0 { 265 switch { 266 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]): 267 feature := take(&required) 268 if exceptionSet[feature] { 269 // An "unfortunate" case: the feature was once 270 // included in the API (e.g. go1.txt), but was 271 // subsequently removed. These are already 272 // acknowledged by being in the file 273 // "api/except.txt". No need to print them out 274 // here. 275 } else if featureSet[featureWithoutContext(feature)] { 276 // okay. 277 } else { 278 fmt.Fprintf(w, "-%s\n", feature) 279 ok = false // broke compatibility 280 } 281 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]): 282 newFeature := take(&features) 283 if optionalSet[newFeature] { 284 // Known added feature to the upcoming release. 285 // Delete it from the map so we can detect any upcoming features 286 // which were never seen. (so we can clean up the nextFile) 287 delete(optionalSet, newFeature) 288 } else { 289 fmt.Fprintf(w, "+%s\n", newFeature) 290 if !*allowNew || !strings.Contains(runtime.Version(), "devel") { 291 ok = false // we're in lock-down mode for next release 292 } 293 } 294 default: 295 take(&required) 296 take(&features) 297 } 298 } 299 300 // In next file, but not in API. 301 var missing []string 302 for feature := range optionalSet { 303 missing = append(missing, feature) 304 } 305 sort.Strings(missing) 306 for _, feature := range missing { 307 fmt.Fprintf(w, "±%s\n", feature) 308 } 309 return 310 } 311 312 func fileFeatures(filename string) []string { 313 if filename == "" { 314 return nil 315 } 316 bs, err := ioutil.ReadFile(filename) 317 if err != nil { 318 log.Fatalf("Error reading file %s: %v", filename, err) 319 } 320 lines := strings.Split(string(bs), "\n") 321 var nonblank []string 322 for _, line := range lines { 323 line = strings.TrimSpace(line) 324 if line != "" && !strings.HasPrefix(line, "#") { 325 nonblank = append(nonblank, line) 326 } 327 } 328 return nonblank 329 } 330 331 var fset = token.NewFileSet() 332 333 type Walker struct { 334 context *build.Context 335 root string 336 scope []string 337 current *types.Package 338 features map[string]bool // set 339 imported map[string]*types.Package // packages already imported 340 } 341 342 func NewWalker(context *build.Context, root string) *Walker { 343 return &Walker{ 344 context: context, 345 root: root, 346 features: map[string]bool{}, 347 imported: map[string]*types.Package{"unsafe": types.Unsafe}, 348 } 349 } 350 351 func (w *Walker) Features() (fs []string) { 352 for f := range w.features { 353 fs = append(fs, f) 354 } 355 sort.Strings(fs) 356 return 357 } 358 359 var parsedFileCache = make(map[string]*ast.File) 360 361 func (w *Walker) parseFile(dir, file string) (*ast.File, error) { 362 filename := filepath.Join(dir, file) 363 f, _ := parsedFileCache[filename] 364 if f != nil { 365 return f, nil 366 } 367 368 var err error 369 370 // generate missing context-dependent files. 371 372 if w.context != nil && file == fmt.Sprintf("zgoos_%s.go", w.context.GOOS) { 373 src := fmt.Sprintf("package runtime; const theGoos = `%s`", w.context.GOOS) 374 f, err = parser.ParseFile(fset, filename, src, 0) 375 if err != nil { 376 log.Fatalf("incorrect generated file: %s", err) 377 } 378 } 379 380 if w.context != nil && file == fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH) { 381 src := fmt.Sprintf("package runtime; const theGoarch = `%s`", w.context.GOARCH) 382 f, err = parser.ParseFile(fset, filename, src, 0) 383 if err != nil { 384 log.Fatalf("incorrect generated file: %s", err) 385 } 386 } 387 if w.context != nil && file == fmt.Sprintf("zruntime_defs_%s_%s.go", w.context.GOOS, w.context.GOARCH) { 388 // Just enough to keep the api checker happy. Keep sorted. 389 src := "package runtime; type (" + 390 " _defer struct{};" + 391 " _func struct{};" + 392 " _panic struct{};" + 393 " _select struct{}; " + 394 " _type struct{};" + 395 " alg struct{};" + 396 " chantype struct{};" + 397 " context struct{};" + // windows 398 " eface struct{};" + 399 " epollevent struct{};" + 400 " funcval struct{};" + 401 " g struct{};" + 402 " gobuf struct{};" + 403 " hchan struct{};" + 404 " iface struct{};" + 405 " interfacetype struct{};" + 406 " itab struct{};" + 407 " keventt struct{};" + 408 " m struct{};" + 409 " maptype struct{};" + 410 " mcache struct{};" + 411 " mspan struct{};" + 412 " mutex struct{};" + 413 " note struct{};" + 414 " p struct{};" + 415 " parfor struct{};" + 416 " slicetype struct{};" + 417 " stkframe struct{};" + 418 " sudog struct{};" + 419 " timespec struct{};" + 420 " waitq struct{};" + 421 " wincallbackcontext struct{};" + 422 "); " + 423 "const (" + 424 " cb_max = 2000;" + 425 " _CacheLineSize = 64;" + 426 " _Gidle = 1;" + 427 " _Grunnable = 2;" + 428 " _Grunning = 3;" + 429 " _Gsyscall = 4;" + 430 " _Gwaiting = 5;" + 431 " _Gdead = 6;" + 432 " _Genqueue = 7;" + 433 " _Gcopystack = 8;" + 434 " _NSIG = 32;" + 435 " _FlagNoScan = iota;" + 436 " _FlagNoZero;" + 437 " _TinySize;" + 438 " _TinySizeClass;" + 439 " _MaxSmallSize;" + 440 " _PageShift;" + 441 " _PageSize;" + 442 " _PageMask;" + 443 " _BitsPerPointer;" + 444 " _BitsMask;" + 445 " _PointersPerByte;" + 446 " _MaxGCMask;" + 447 " _BitsDead;" + 448 " _BitsPointer;" + 449 " _MSpanInUse;" + 450 " _ConcurrentSweep;" + 451 " _KindBool;" + 452 " _KindInt;" + 453 " _KindInt8;" + 454 " _KindInt16;" + 455 " _KindInt32;" + 456 " _KindInt64;" + 457 " _KindUint;" + 458 " _KindUint8;" + 459 " _KindUint16;" + 460 " _KindUint32;" + 461 " _KindUint64;" + 462 " _KindUintptr;" + 463 " _KindFloat32;" + 464 " _KindFloat64;" + 465 " _KindComplex64;" + 466 " _KindComplex128;" + 467 " _KindArray;" + 468 " _KindChan;" + 469 " _KindFunc;" + 470 " _KindInterface;" + 471 " _KindMap;" + 472 " _KindPtr;" + 473 " _KindSlice;" + 474 " _KindString;" + 475 " _KindStruct;" + 476 " _KindUnsafePointer;" + 477 " _KindDirectIface;" + 478 " _KindGCProg;" + 479 " _KindNoPointers;" + 480 " _KindMask;" + 481 ")" 482 f, err = parser.ParseFile(fset, filename, src, 0) 483 if err != nil { 484 log.Fatalf("incorrect generated file: %s", err) 485 } 486 } 487 488 if f == nil { 489 f, err = parser.ParseFile(fset, filename, nil, 0) 490 if err != nil { 491 return nil, err 492 } 493 } 494 495 parsedFileCache[filename] = f 496 return f, nil 497 } 498 499 func contains(list []string, s string) bool { 500 for _, t := range list { 501 if t == s { 502 return true 503 } 504 } 505 return false 506 } 507 508 // The package cache doesn't operate correctly in rare (so far artificial) 509 // circumstances (issue 8425). Disable before debugging non-obvious errors 510 // from the type-checker. 511 const usePkgCache = true 512 513 var ( 514 pkgCache = map[string]*types.Package{} // map tagKey to package 515 pkgTags = map[string][]string{} // map import dir to list of relevant tags 516 ) 517 518 // tagKey returns the tag-based key to use in the pkgCache. 519 // It is a comma-separated string; the first part is dir, the rest tags. 520 // The satisfied tags are derived from context but only those that 521 // matter (the ones listed in the tags argument) are used. 522 // The tags list, which came from go/build's Package.AllTags, 523 // is known to be sorted. 524 func tagKey(dir string, context *build.Context, tags []string) string { 525 ctags := map[string]bool{ 526 context.GOOS: true, 527 context.GOARCH: true, 528 } 529 if context.CgoEnabled { 530 ctags["cgo"] = true 531 } 532 for _, tag := range context.BuildTags { 533 ctags[tag] = true 534 } 535 // TODO: ReleaseTags (need to load default) 536 key := dir 537 for _, tag := range tags { 538 if ctags[tag] { 539 key += "," + tag 540 } 541 } 542 return key 543 } 544 545 // Importing is a sentinel taking the place in Walker.imported 546 // for a package that is in the process of being imported. 547 var importing types.Package 548 549 func (w *Walker) Import(name string) (pkg *types.Package) { 550 pkg = w.imported[name] 551 if pkg != nil { 552 if pkg == &importing { 553 log.Fatalf("cycle importing package %q", name) 554 } 555 return pkg 556 } 557 w.imported[name] = &importing 558 559 // Determine package files. 560 dir := filepath.Join(w.root, filepath.FromSlash(name)) 561 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() { 562 log.Fatalf("no source in tree for package %q", pkg) 563 } 564 565 context := w.context 566 if context == nil { 567 context = &build.Default 568 } 569 570 // Look in cache. 571 // If we've already done an import with the same set 572 // of relevant tags, reuse the result. 573 var key string 574 if usePkgCache { 575 if tags, ok := pkgTags[dir]; ok { 576 key = tagKey(dir, context, tags) 577 if pkg := pkgCache[key]; pkg != nil { 578 w.imported[name] = pkg 579 return pkg 580 } 581 } 582 } 583 584 info, err := context.ImportDir(dir, 0) 585 if err != nil { 586 if _, nogo := err.(*build.NoGoError); nogo { 587 return 588 } 589 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) 590 } 591 592 // Save tags list first time we see a directory. 593 if usePkgCache { 594 if _, ok := pkgTags[dir]; !ok { 595 pkgTags[dir] = info.AllTags 596 key = tagKey(dir, context, info.AllTags) 597 } 598 } 599 600 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...) 601 602 // Certain files only exist when building for the specified context. 603 // Add them manually. 604 if name == "runtime" { 605 n := fmt.Sprintf("zgoos_%s.go", w.context.GOOS) 606 if !contains(filenames, n) { 607 filenames = append(filenames, n) 608 } 609 610 n = fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH) 611 if !contains(filenames, n) { 612 filenames = append(filenames, n) 613 } 614 615 n = fmt.Sprintf("zruntime_defs_%s_%s.go", w.context.GOOS, w.context.GOARCH) 616 if !contains(filenames, n) { 617 filenames = append(filenames, n) 618 } 619 } 620 621 // Parse package files. 622 var files []*ast.File 623 for _, file := range filenames { 624 f, err := w.parseFile(dir, file) 625 if err != nil { 626 log.Fatalf("error parsing package %s: %s", name, err) 627 } 628 files = append(files, f) 629 } 630 631 // Type-check package files. 632 conf := types.Config{ 633 IgnoreFuncBodies: true, 634 FakeImportC: true, 635 Import: func(imports map[string]*types.Package, name string) (*types.Package, error) { 636 pkg := w.Import(name) 637 imports[name] = pkg 638 return pkg, nil 639 }, 640 } 641 pkg, err = conf.Check(name, fset, files, nil) 642 if err != nil { 643 ctxt := "<no context>" 644 if w.context != nil { 645 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH) 646 } 647 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt) 648 } 649 650 if usePkgCache { 651 pkgCache[key] = pkg 652 } 653 654 w.imported[name] = pkg 655 return 656 } 657 658 // pushScope enters a new scope (walking a package, type, node, etc) 659 // and returns a function that will leave the scope (with sanity checking 660 // for mismatched pushes & pops) 661 func (w *Walker) pushScope(name string) (popFunc func()) { 662 w.scope = append(w.scope, name) 663 return func() { 664 if len(w.scope) == 0 { 665 log.Fatalf("attempt to leave scope %q with empty scope list", name) 666 } 667 if w.scope[len(w.scope)-1] != name { 668 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope) 669 } 670 w.scope = w.scope[:len(w.scope)-1] 671 } 672 } 673 674 func sortedMethodNames(typ *types.Interface) []string { 675 n := typ.NumMethods() 676 list := make([]string, n) 677 for i := range list { 678 list[i] = typ.Method(i).Name() 679 } 680 sort.Strings(list) 681 return list 682 } 683 684 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { 685 switch typ := typ.(type) { 686 case *types.Basic: 687 s := typ.Name() 688 switch typ.Kind() { 689 case types.UnsafePointer: 690 s = "unsafe.Pointer" 691 case types.UntypedBool: 692 s = "ideal-bool" 693 case types.UntypedInt: 694 s = "ideal-int" 695 case types.UntypedRune: 696 // "ideal-char" for compatibility with old tool 697 // TODO(gri) change to "ideal-rune" 698 s = "ideal-char" 699 case types.UntypedFloat: 700 s = "ideal-float" 701 case types.UntypedComplex: 702 s = "ideal-complex" 703 case types.UntypedString: 704 s = "ideal-string" 705 case types.UntypedNil: 706 panic("should never see untyped nil type") 707 default: 708 switch s { 709 case "byte": 710 s = "uint8" 711 case "rune": 712 s = "int32" 713 } 714 } 715 buf.WriteString(s) 716 717 case *types.Array: 718 fmt.Fprintf(buf, "[%d]", typ.Len()) 719 w.writeType(buf, typ.Elem()) 720 721 case *types.Slice: 722 buf.WriteString("[]") 723 w.writeType(buf, typ.Elem()) 724 725 case *types.Struct: 726 buf.WriteString("struct") 727 728 case *types.Pointer: 729 buf.WriteByte('*') 730 w.writeType(buf, typ.Elem()) 731 732 case *types.Tuple: 733 panic("should never see a tuple type") 734 735 case *types.Signature: 736 buf.WriteString("func") 737 w.writeSignature(buf, typ) 738 739 case *types.Interface: 740 buf.WriteString("interface{") 741 if typ.NumMethods() > 0 { 742 buf.WriteByte(' ') 743 buf.WriteString(strings.Join(sortedMethodNames(typ), ", ")) 744 buf.WriteByte(' ') 745 } 746 buf.WriteString("}") 747 748 case *types.Map: 749 buf.WriteString("map[") 750 w.writeType(buf, typ.Key()) 751 buf.WriteByte(']') 752 w.writeType(buf, typ.Elem()) 753 754 case *types.Chan: 755 var s string 756 switch typ.Dir() { 757 case ast.SEND: 758 s = "chan<- " 759 case ast.RECV: 760 s = "<-chan " 761 default: 762 s = "chan " 763 } 764 buf.WriteString(s) 765 w.writeType(buf, typ.Elem()) 766 767 case *types.Named: 768 obj := typ.Obj() 769 pkg := obj.Pkg() 770 if pkg != nil && pkg != w.current { 771 buf.WriteString(pkg.Name()) 772 buf.WriteByte('.') 773 } 774 buf.WriteString(typ.Obj().Name()) 775 776 default: 777 panic(fmt.Sprintf("unknown type %T", typ)) 778 } 779 } 780 781 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) { 782 w.writeParams(buf, sig.Params(), sig.IsVariadic()) 783 switch res := sig.Results(); res.Len() { 784 case 0: 785 // nothing to do 786 case 1: 787 buf.WriteByte(' ') 788 w.writeType(buf, res.At(0).Type()) 789 default: 790 buf.WriteByte(' ') 791 w.writeParams(buf, res, false) 792 } 793 } 794 795 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) { 796 buf.WriteByte('(') 797 for i, n := 0, t.Len(); i < n; i++ { 798 if i > 0 { 799 buf.WriteString(", ") 800 } 801 typ := t.At(i).Type() 802 if variadic && i+1 == n { 803 buf.WriteString("...") 804 typ = typ.(*types.Slice).Elem() 805 } 806 w.writeType(buf, typ) 807 } 808 buf.WriteByte(')') 809 } 810 811 func (w *Walker) typeString(typ types.Type) string { 812 var buf bytes.Buffer 813 w.writeType(&buf, typ) 814 return buf.String() 815 } 816 817 func (w *Walker) signatureString(sig *types.Signature) string { 818 var buf bytes.Buffer 819 w.writeSignature(&buf, sig) 820 return buf.String() 821 } 822 823 func (w *Walker) emitObj(obj types.Object) { 824 switch obj := obj.(type) { 825 case *types.Const: 826 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) 827 w.emitf("const %s = %s", obj.Name(), obj.Val()) 828 case *types.Var: 829 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) 830 case *types.TypeName: 831 w.emitType(obj) 832 case *types.Func: 833 w.emitFunc(obj) 834 default: 835 panic("unknown object: " + obj.String()) 836 } 837 } 838 839 func (w *Walker) emitType(obj *types.TypeName) { 840 name := obj.Name() 841 typ := obj.Type() 842 switch typ := typ.Underlying().(type) { 843 case *types.Struct: 844 w.emitStructType(name, typ) 845 case *types.Interface: 846 w.emitIfaceType(name, typ) 847 return // methods are handled by emitIfaceType 848 default: 849 w.emitf("type %s %s", name, w.typeString(typ.Underlying())) 850 } 851 852 // emit methods with value receiver 853 var methodNames map[string]bool 854 vset := typ.MethodSet() 855 for i, n := 0, vset.Len(); i < n; i++ { 856 m := vset.At(i) 857 if m.Obj().IsExported() { 858 w.emitMethod(m) 859 if methodNames == nil { 860 methodNames = make(map[string]bool) 861 } 862 methodNames[m.Obj().Name()] = true 863 } 864 } 865 866 // emit methods with pointer receiver; exclude 867 // methods that we have emitted already 868 // (the method set of *T includes the methods of T) 869 pset := types.NewPointer(typ).MethodSet() 870 for i, n := 0, pset.Len(); i < n; i++ { 871 m := pset.At(i) 872 if m.Obj().IsExported() && !methodNames[m.Obj().Name()] { 873 w.emitMethod(m) 874 } 875 } 876 } 877 878 func (w *Walker) emitStructType(name string, typ *types.Struct) { 879 typeStruct := fmt.Sprintf("type %s struct", name) 880 w.emitf(typeStruct) 881 defer w.pushScope(typeStruct)() 882 883 for i := 0; i < typ.NumFields(); i++ { 884 f := typ.Field(i) 885 if !f.IsExported() { 886 continue 887 } 888 typ := f.Type() 889 if f.Anonymous() { 890 w.emitf("embedded %s", w.typeString(typ)) 891 continue 892 } 893 w.emitf("%s %s", f.Name(), w.typeString(typ)) 894 } 895 } 896 897 func (w *Walker) emitIfaceType(name string, typ *types.Interface) { 898 pop := w.pushScope("type " + name + " interface") 899 900 var methodNames []string 901 complete := true 902 mset := typ.MethodSet() 903 for i, n := 0, mset.Len(); i < n; i++ { 904 m := mset.At(i).Obj().(*types.Func) 905 if !m.IsExported() { 906 complete = false 907 continue 908 } 909 methodNames = append(methodNames, m.Name()) 910 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature))) 911 } 912 913 if !complete { 914 // The method set has unexported methods, so all the 915 // implementations are provided by the same package, 916 // so the method set can be extended. Instead of recording 917 // the full set of names (below), record only that there were 918 // unexported methods. (If the interface shrinks, we will notice 919 // because a method signature emitted during the last loop 920 // will disappear.) 921 w.emitf("unexported methods") 922 } 923 924 pop() 925 926 if !complete { 927 return 928 } 929 930 if len(methodNames) == 0 { 931 w.emitf("type %s interface {}", name) 932 return 933 } 934 935 sort.Strings(methodNames) 936 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", ")) 937 } 938 939 func (w *Walker) emitFunc(f *types.Func) { 940 sig := f.Type().(*types.Signature) 941 if sig.Recv() != nil { 942 panic("method considered a regular function: " + f.String()) 943 } 944 w.emitf("func %s%s", f.Name(), w.signatureString(sig)) 945 } 946 947 func (w *Walker) emitMethod(m *types.Selection) { 948 sig := m.Type().(*types.Signature) 949 recv := sig.Recv().Type() 950 // report exported methods with unexported receiver base type 951 if true { 952 base := recv 953 if p, _ := recv.(*types.Pointer); p != nil { 954 base = p.Elem() 955 } 956 if obj := base.(*types.Named).Obj(); !obj.IsExported() { 957 log.Fatalf("exported method with unexported receiver base type: %s", m) 958 } 959 } 960 w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig)) 961 } 962 963 func (w *Walker) emitf(format string, args ...interface{}) { 964 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...) 965 if strings.Contains(f, "\n") { 966 panic("feature contains newlines: " + f) 967 } 968 969 if _, dup := w.features[f]; dup { 970 panic("duplicate feature inserted: " + f) 971 } 972 w.features[f] = true 973 974 if *verbose { 975 log.Printf("feature: %s", f) 976 } 977 }