github.com/zach-klippenstein/go@v0.0.0-20150108044943-fcfbeb3adf58/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 ( 111 internalPkg = regexp.MustCompile(`(^|/)internal($|/)`) 112 hashRx = regexp.MustCompile(`^[0-9a-f]{7,40}$`) 113 ) 114 115 func isDevelVersion(v string) bool { 116 if strings.Contains(v, "devel") { 117 return true 118 } 119 return hashRx.MatchString(v) 120 } 121 122 func main() { 123 flag.Parse() 124 125 if v := runtime.Version(); !strings.Contains(v, "weekly") && !isDevelVersion(v) { 126 if *nextFile != "" { 127 fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile) 128 *nextFile = "" 129 } 130 } 131 132 if *forceCtx != "" { 133 setContexts() 134 } 135 for _, c := range contexts { 136 c.Compiler = build.Default.Compiler 137 } 138 139 var pkgNames []string 140 if flag.NArg() > 0 { 141 pkgNames = flag.Args() 142 } else { 143 stds, err := exec.Command("go", "list", "std").Output() 144 if err != nil { 145 log.Fatal(err) 146 } 147 for _, pkg := range strings.Fields(string(stds)) { 148 if !internalPkg.MatchString(pkg) { 149 pkgNames = append(pkgNames, pkg) 150 } 151 } 152 } 153 154 var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true 155 for _, context := range contexts { 156 w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src")) 157 158 for _, name := range pkgNames { 159 // - Package "unsafe" contains special signatures requiring 160 // extra care when printing them - ignore since it is not 161 // going to change w/o a language change. 162 // - We don't care about the API of commands. 163 if name != "unsafe" && !strings.HasPrefix(name, "cmd/") { 164 if name == "runtime/cgo" && !context.CgoEnabled { 165 // w.Import(name) will return nil 166 continue 167 } 168 w.export(w.Import(name)) 169 } 170 } 171 172 ctxName := contextName(context) 173 for _, f := range w.Features() { 174 if featureCtx[f] == nil { 175 featureCtx[f] = make(map[string]bool) 176 } 177 featureCtx[f][ctxName] = true 178 } 179 } 180 181 var features []string 182 for f, cmap := range featureCtx { 183 if len(cmap) == len(contexts) { 184 features = append(features, f) 185 continue 186 } 187 comma := strings.Index(f, ",") 188 for cname := range cmap { 189 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:]) 190 features = append(features, f2) 191 } 192 } 193 194 fail := false 195 defer func() { 196 if fail { 197 os.Exit(1) 198 } 199 }() 200 201 bw := bufio.NewWriter(os.Stdout) 202 defer bw.Flush() 203 204 if *checkFile == "" { 205 sort.Strings(features) 206 for _, f := range features { 207 fmt.Fprintln(bw, f) 208 } 209 return 210 } 211 212 var required []string 213 for _, file := range strings.Split(*checkFile, ",") { 214 required = append(required, fileFeatures(file)...) 215 } 216 optional := fileFeatures(*nextFile) 217 exception := fileFeatures(*exceptFile) 218 fail = !compareAPI(bw, features, required, optional, exception) 219 } 220 221 // export emits the exported package features. 222 func (w *Walker) export(pkg *types.Package) { 223 if *verbose { 224 log.Println(pkg) 225 } 226 pop := w.pushScope("pkg " + pkg.Path()) 227 w.current = pkg 228 scope := pkg.Scope() 229 for _, name := range scope.Names() { 230 if ast.IsExported(name) { 231 w.emitObj(scope.Lookup(name)) 232 } 233 } 234 pop() 235 } 236 237 func set(items []string) map[string]bool { 238 s := make(map[string]bool) 239 for _, v := range items { 240 s[v] = true 241 } 242 return s 243 } 244 245 var spaceParensRx = regexp.MustCompile(` \(\S+?\)`) 246 247 func featureWithoutContext(f string) string { 248 if !strings.Contains(f, "(") { 249 return f 250 } 251 return spaceParensRx.ReplaceAllString(f, "") 252 } 253 254 func compareAPI(w io.Writer, features, required, optional, exception []string) (ok bool) { 255 ok = true 256 257 optionalSet := set(optional) 258 exceptionSet := set(exception) 259 featureSet := set(features) 260 261 sort.Strings(features) 262 sort.Strings(required) 263 264 take := func(sl *[]string) string { 265 s := (*sl)[0] 266 *sl = (*sl)[1:] 267 return s 268 } 269 270 for len(required) > 0 || len(features) > 0 { 271 switch { 272 case len(features) == 0 || (len(required) > 0 && required[0] < features[0]): 273 feature := take(&required) 274 if exceptionSet[feature] { 275 // An "unfortunate" case: the feature was once 276 // included in the API (e.g. go1.txt), but was 277 // subsequently removed. These are already 278 // acknowledged by being in the file 279 // "api/except.txt". No need to print them out 280 // here. 281 } else if featureSet[featureWithoutContext(feature)] { 282 // okay. 283 } else { 284 fmt.Fprintf(w, "-%s\n", feature) 285 ok = false // broke compatibility 286 } 287 case len(required) == 0 || (len(features) > 0 && required[0] > features[0]): 288 newFeature := take(&features) 289 if optionalSet[newFeature] { 290 // Known added feature to the upcoming release. 291 // Delete it from the map so we can detect any upcoming features 292 // which were never seen. (so we can clean up the nextFile) 293 delete(optionalSet, newFeature) 294 } else { 295 fmt.Fprintf(w, "+%s\n", newFeature) 296 if !*allowNew || !isDevelVersion(runtime.Version()) { 297 ok = false // we're in lock-down mode for next release 298 } 299 } 300 default: 301 take(&required) 302 take(&features) 303 } 304 } 305 306 // In next file, but not in API. 307 var missing []string 308 for feature := range optionalSet { 309 missing = append(missing, feature) 310 } 311 sort.Strings(missing) 312 for _, feature := range missing { 313 fmt.Fprintf(w, "±%s\n", feature) 314 } 315 return 316 } 317 318 func fileFeatures(filename string) []string { 319 if filename == "" { 320 return nil 321 } 322 bs, err := ioutil.ReadFile(filename) 323 if err != nil { 324 log.Fatalf("Error reading file %s: %v", filename, err) 325 } 326 lines := strings.Split(string(bs), "\n") 327 var nonblank []string 328 for _, line := range lines { 329 line = strings.TrimSpace(line) 330 if line != "" && !strings.HasPrefix(line, "#") { 331 nonblank = append(nonblank, line) 332 } 333 } 334 return nonblank 335 } 336 337 var fset = token.NewFileSet() 338 339 type Walker struct { 340 context *build.Context 341 root string 342 scope []string 343 current *types.Package 344 features map[string]bool // set 345 imported map[string]*types.Package // packages already imported 346 } 347 348 func NewWalker(context *build.Context, root string) *Walker { 349 return &Walker{ 350 context: context, 351 root: root, 352 features: map[string]bool{}, 353 imported: map[string]*types.Package{"unsafe": types.Unsafe}, 354 } 355 } 356 357 func (w *Walker) Features() (fs []string) { 358 for f := range w.features { 359 fs = append(fs, f) 360 } 361 sort.Strings(fs) 362 return 363 } 364 365 var parsedFileCache = make(map[string]*ast.File) 366 367 func (w *Walker) parseFile(dir, file string) (*ast.File, error) { 368 filename := filepath.Join(dir, file) 369 if f := parsedFileCache[filename]; f != nil { 370 return f, nil 371 } 372 373 f, err := parser.ParseFile(fset, filename, nil, 0) 374 if err != nil { 375 return nil, err 376 } 377 parsedFileCache[filename] = f 378 379 return f, nil 380 } 381 382 func contains(list []string, s string) bool { 383 for _, t := range list { 384 if t == s { 385 return true 386 } 387 } 388 return false 389 } 390 391 // The package cache doesn't operate correctly in rare (so far artificial) 392 // circumstances (issue 8425). Disable before debugging non-obvious errors 393 // from the type-checker. 394 const usePkgCache = true 395 396 var ( 397 pkgCache = map[string]*types.Package{} // map tagKey to package 398 pkgTags = map[string][]string{} // map import dir to list of relevant tags 399 ) 400 401 // tagKey returns the tag-based key to use in the pkgCache. 402 // It is a comma-separated string; the first part is dir, the rest tags. 403 // The satisfied tags are derived from context but only those that 404 // matter (the ones listed in the tags argument) are used. 405 // The tags list, which came from go/build's Package.AllTags, 406 // is known to be sorted. 407 func tagKey(dir string, context *build.Context, tags []string) string { 408 ctags := map[string]bool{ 409 context.GOOS: true, 410 context.GOARCH: true, 411 } 412 if context.CgoEnabled { 413 ctags["cgo"] = true 414 } 415 for _, tag := range context.BuildTags { 416 ctags[tag] = true 417 } 418 // TODO: ReleaseTags (need to load default) 419 key := dir 420 for _, tag := range tags { 421 if ctags[tag] { 422 key += "," + tag 423 } 424 } 425 return key 426 } 427 428 // Importing is a sentinel taking the place in Walker.imported 429 // for a package that is in the process of being imported. 430 var importing types.Package 431 432 func (w *Walker) Import(name string) (pkg *types.Package) { 433 pkg = w.imported[name] 434 if pkg != nil { 435 if pkg == &importing { 436 log.Fatalf("cycle importing package %q", name) 437 } 438 return pkg 439 } 440 w.imported[name] = &importing 441 442 // Determine package files. 443 dir := filepath.Join(w.root, filepath.FromSlash(name)) 444 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() { 445 log.Fatalf("no source in tree for package %q", pkg) 446 } 447 448 context := w.context 449 if context == nil { 450 context = &build.Default 451 } 452 453 // Look in cache. 454 // If we've already done an import with the same set 455 // of relevant tags, reuse the result. 456 var key string 457 if usePkgCache { 458 if tags, ok := pkgTags[dir]; ok { 459 key = tagKey(dir, context, tags) 460 if pkg := pkgCache[key]; pkg != nil { 461 w.imported[name] = pkg 462 return pkg 463 } 464 } 465 } 466 467 info, err := context.ImportDir(dir, 0) 468 if err != nil { 469 if _, nogo := err.(*build.NoGoError); nogo { 470 return 471 } 472 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) 473 } 474 475 // Save tags list first time we see a directory. 476 if usePkgCache { 477 if _, ok := pkgTags[dir]; !ok { 478 pkgTags[dir] = info.AllTags 479 key = tagKey(dir, context, info.AllTags) 480 } 481 } 482 483 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...) 484 485 // Parse package files. 486 var files []*ast.File 487 for _, file := range filenames { 488 f, err := w.parseFile(dir, file) 489 if err != nil { 490 log.Fatalf("error parsing package %s: %s", name, err) 491 } 492 files = append(files, f) 493 } 494 495 // Type-check package files. 496 conf := types.Config{ 497 IgnoreFuncBodies: true, 498 FakeImportC: true, 499 Import: func(imports map[string]*types.Package, name string) (*types.Package, error) { 500 pkg := w.Import(name) 501 imports[name] = pkg 502 return pkg, nil 503 }, 504 } 505 pkg, err = conf.Check(name, fset, files, nil) 506 if err != nil { 507 ctxt := "<no context>" 508 if w.context != nil { 509 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH) 510 } 511 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt) 512 } 513 514 if usePkgCache { 515 pkgCache[key] = pkg 516 } 517 518 w.imported[name] = pkg 519 return 520 } 521 522 // pushScope enters a new scope (walking a package, type, node, etc) 523 // and returns a function that will leave the scope (with sanity checking 524 // for mismatched pushes & pops) 525 func (w *Walker) pushScope(name string) (popFunc func()) { 526 w.scope = append(w.scope, name) 527 return func() { 528 if len(w.scope) == 0 { 529 log.Fatalf("attempt to leave scope %q with empty scope list", name) 530 } 531 if w.scope[len(w.scope)-1] != name { 532 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope) 533 } 534 w.scope = w.scope[:len(w.scope)-1] 535 } 536 } 537 538 func sortedMethodNames(typ *types.Interface) []string { 539 n := typ.NumMethods() 540 list := make([]string, n) 541 for i := range list { 542 list[i] = typ.Method(i).Name() 543 } 544 sort.Strings(list) 545 return list 546 } 547 548 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { 549 switch typ := typ.(type) { 550 case *types.Basic: 551 s := typ.Name() 552 switch typ.Kind() { 553 case types.UnsafePointer: 554 s = "unsafe.Pointer" 555 case types.UntypedBool: 556 s = "ideal-bool" 557 case types.UntypedInt: 558 s = "ideal-int" 559 case types.UntypedRune: 560 // "ideal-char" for compatibility with old tool 561 // TODO(gri) change to "ideal-rune" 562 s = "ideal-char" 563 case types.UntypedFloat: 564 s = "ideal-float" 565 case types.UntypedComplex: 566 s = "ideal-complex" 567 case types.UntypedString: 568 s = "ideal-string" 569 case types.UntypedNil: 570 panic("should never see untyped nil type") 571 default: 572 switch s { 573 case "byte": 574 s = "uint8" 575 case "rune": 576 s = "int32" 577 } 578 } 579 buf.WriteString(s) 580 581 case *types.Array: 582 fmt.Fprintf(buf, "[%d]", typ.Len()) 583 w.writeType(buf, typ.Elem()) 584 585 case *types.Slice: 586 buf.WriteString("[]") 587 w.writeType(buf, typ.Elem()) 588 589 case *types.Struct: 590 buf.WriteString("struct") 591 592 case *types.Pointer: 593 buf.WriteByte('*') 594 w.writeType(buf, typ.Elem()) 595 596 case *types.Tuple: 597 panic("should never see a tuple type") 598 599 case *types.Signature: 600 buf.WriteString("func") 601 w.writeSignature(buf, typ) 602 603 case *types.Interface: 604 buf.WriteString("interface{") 605 if typ.NumMethods() > 0 { 606 buf.WriteByte(' ') 607 buf.WriteString(strings.Join(sortedMethodNames(typ), ", ")) 608 buf.WriteByte(' ') 609 } 610 buf.WriteString("}") 611 612 case *types.Map: 613 buf.WriteString("map[") 614 w.writeType(buf, typ.Key()) 615 buf.WriteByte(']') 616 w.writeType(buf, typ.Elem()) 617 618 case *types.Chan: 619 var s string 620 switch typ.Dir() { 621 case ast.SEND: 622 s = "chan<- " 623 case ast.RECV: 624 s = "<-chan " 625 default: 626 s = "chan " 627 } 628 buf.WriteString(s) 629 w.writeType(buf, typ.Elem()) 630 631 case *types.Named: 632 obj := typ.Obj() 633 pkg := obj.Pkg() 634 if pkg != nil && pkg != w.current { 635 buf.WriteString(pkg.Name()) 636 buf.WriteByte('.') 637 } 638 buf.WriteString(typ.Obj().Name()) 639 640 default: 641 panic(fmt.Sprintf("unknown type %T", typ)) 642 } 643 } 644 645 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) { 646 w.writeParams(buf, sig.Params(), sig.IsVariadic()) 647 switch res := sig.Results(); res.Len() { 648 case 0: 649 // nothing to do 650 case 1: 651 buf.WriteByte(' ') 652 w.writeType(buf, res.At(0).Type()) 653 default: 654 buf.WriteByte(' ') 655 w.writeParams(buf, res, false) 656 } 657 } 658 659 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) { 660 buf.WriteByte('(') 661 for i, n := 0, t.Len(); i < n; i++ { 662 if i > 0 { 663 buf.WriteString(", ") 664 } 665 typ := t.At(i).Type() 666 if variadic && i+1 == n { 667 buf.WriteString("...") 668 typ = typ.(*types.Slice).Elem() 669 } 670 w.writeType(buf, typ) 671 } 672 buf.WriteByte(')') 673 } 674 675 func (w *Walker) typeString(typ types.Type) string { 676 var buf bytes.Buffer 677 w.writeType(&buf, typ) 678 return buf.String() 679 } 680 681 func (w *Walker) signatureString(sig *types.Signature) string { 682 var buf bytes.Buffer 683 w.writeSignature(&buf, sig) 684 return buf.String() 685 } 686 687 func (w *Walker) emitObj(obj types.Object) { 688 switch obj := obj.(type) { 689 case *types.Const: 690 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) 691 w.emitf("const %s = %s", obj.Name(), obj.Val()) 692 case *types.Var: 693 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) 694 case *types.TypeName: 695 w.emitType(obj) 696 case *types.Func: 697 w.emitFunc(obj) 698 default: 699 panic("unknown object: " + obj.String()) 700 } 701 } 702 703 func (w *Walker) emitType(obj *types.TypeName) { 704 name := obj.Name() 705 typ := obj.Type() 706 switch typ := typ.Underlying().(type) { 707 case *types.Struct: 708 w.emitStructType(name, typ) 709 case *types.Interface: 710 w.emitIfaceType(name, typ) 711 return // methods are handled by emitIfaceType 712 default: 713 w.emitf("type %s %s", name, w.typeString(typ.Underlying())) 714 } 715 716 // emit methods with value receiver 717 var methodNames map[string]bool 718 vset := typ.MethodSet() 719 for i, n := 0, vset.Len(); i < n; i++ { 720 m := vset.At(i) 721 if m.Obj().IsExported() { 722 w.emitMethod(m) 723 if methodNames == nil { 724 methodNames = make(map[string]bool) 725 } 726 methodNames[m.Obj().Name()] = true 727 } 728 } 729 730 // emit methods with pointer receiver; exclude 731 // methods that we have emitted already 732 // (the method set of *T includes the methods of T) 733 pset := types.NewPointer(typ).MethodSet() 734 for i, n := 0, pset.Len(); i < n; i++ { 735 m := pset.At(i) 736 if m.Obj().IsExported() && !methodNames[m.Obj().Name()] { 737 w.emitMethod(m) 738 } 739 } 740 } 741 742 func (w *Walker) emitStructType(name string, typ *types.Struct) { 743 typeStruct := fmt.Sprintf("type %s struct", name) 744 w.emitf(typeStruct) 745 defer w.pushScope(typeStruct)() 746 747 for i := 0; i < typ.NumFields(); i++ { 748 f := typ.Field(i) 749 if !f.IsExported() { 750 continue 751 } 752 typ := f.Type() 753 if f.Anonymous() { 754 w.emitf("embedded %s", w.typeString(typ)) 755 continue 756 } 757 w.emitf("%s %s", f.Name(), w.typeString(typ)) 758 } 759 } 760 761 func (w *Walker) emitIfaceType(name string, typ *types.Interface) { 762 pop := w.pushScope("type " + name + " interface") 763 764 var methodNames []string 765 complete := true 766 mset := typ.MethodSet() 767 for i, n := 0, mset.Len(); i < n; i++ { 768 m := mset.At(i).Obj().(*types.Func) 769 if !m.IsExported() { 770 complete = false 771 continue 772 } 773 methodNames = append(methodNames, m.Name()) 774 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature))) 775 } 776 777 if !complete { 778 // The method set has unexported methods, so all the 779 // implementations are provided by the same package, 780 // so the method set can be extended. Instead of recording 781 // the full set of names (below), record only that there were 782 // unexported methods. (If the interface shrinks, we will notice 783 // because a method signature emitted during the last loop 784 // will disappear.) 785 w.emitf("unexported methods") 786 } 787 788 pop() 789 790 if !complete { 791 return 792 } 793 794 if len(methodNames) == 0 { 795 w.emitf("type %s interface {}", name) 796 return 797 } 798 799 sort.Strings(methodNames) 800 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", ")) 801 } 802 803 func (w *Walker) emitFunc(f *types.Func) { 804 sig := f.Type().(*types.Signature) 805 if sig.Recv() != nil { 806 panic("method considered a regular function: " + f.String()) 807 } 808 w.emitf("func %s%s", f.Name(), w.signatureString(sig)) 809 } 810 811 func (w *Walker) emitMethod(m *types.Selection) { 812 sig := m.Type().(*types.Signature) 813 recv := sig.Recv().Type() 814 // report exported methods with unexported receiver base type 815 if true { 816 base := recv 817 if p, _ := recv.(*types.Pointer); p != nil { 818 base = p.Elem() 819 } 820 if obj := base.(*types.Named).Obj(); !obj.IsExported() { 821 log.Fatalf("exported method with unexported receiver base type: %s", m) 822 } 823 } 824 w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig)) 825 } 826 827 func (w *Walker) emitf(format string, args ...interface{}) { 828 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...) 829 if strings.Contains(f, "\n") { 830 panic("feature contains newlines: " + f) 831 } 832 833 if _, dup := w.features[f]; dup { 834 panic("duplicate feature inserted: " + f) 835 } 836 w.features[f] = true 837 838 if *verbose { 839 log.Printf("feature: %s", f) 840 } 841 }