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