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