github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/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 // Flags 31 var ( 32 checkFile = flag.String("c", "", "optional comma-separated filename(s) to check API against") 33 allowNew = flag.Bool("allow_new", true, "allow API additions") 34 exceptFile = flag.String("except", "", "optional filename of packages that are allowed to change without triggering a failure in the tool") 35 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.") 36 verbose = flag.Bool("v", false, "verbose debugging") 37 forceCtx = flag.String("contexts", "", "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts.") 38 ) 39 40 // contexts are the default contexts which are scanned, unless 41 // overridden by the -contexts flag. 42 var contexts = []*build.Context{ 43 {GOOS: "linux", GOARCH: "386", CgoEnabled: true}, 44 {GOOS: "linux", GOARCH: "386"}, 45 {GOOS: "linux", GOARCH: "amd64", CgoEnabled: true}, 46 {GOOS: "linux", GOARCH: "amd64"}, 47 {GOOS: "linux", GOARCH: "arm", CgoEnabled: true}, 48 {GOOS: "linux", GOARCH: "arm"}, 49 {GOOS: "darwin", GOARCH: "386", CgoEnabled: true}, 50 {GOOS: "darwin", GOARCH: "386"}, 51 {GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true}, 52 {GOOS: "darwin", GOARCH: "amd64"}, 53 {GOOS: "windows", GOARCH: "amd64"}, 54 {GOOS: "windows", GOARCH: "386"}, 55 {GOOS: "freebsd", GOARCH: "386", CgoEnabled: true}, 56 {GOOS: "freebsd", GOARCH: "386"}, 57 {GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true}, 58 {GOOS: "freebsd", GOARCH: "amd64"}, 59 {GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true}, 60 {GOOS: "freebsd", GOARCH: "arm"}, 61 {GOOS: "netbsd", GOARCH: "386", CgoEnabled: true}, 62 {GOOS: "netbsd", GOARCH: "386"}, 63 {GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true}, 64 {GOOS: "netbsd", GOARCH: "amd64"}, 65 {GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true}, 66 {GOOS: "netbsd", GOARCH: "arm"}, 67 {GOOS: "openbsd", GOARCH: "386", CgoEnabled: true}, 68 {GOOS: "openbsd", GOARCH: "386"}, 69 {GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true}, 70 {GOOS: "openbsd", GOARCH: "amd64"}, 71 } 72 73 func contextName(c *build.Context) string { 74 s := c.GOOS + "-" + c.GOARCH 75 if c.CgoEnabled { 76 return s + "-cgo" 77 } 78 return s 79 } 80 81 func parseContext(c string) *build.Context { 82 parts := strings.Split(c, "-") 83 if len(parts) < 2 { 84 log.Fatalf("bad context: %q", c) 85 } 86 bc := &build.Context{ 87 GOOS: parts[0], 88 GOARCH: parts[1], 89 } 90 if len(parts) == 3 { 91 if parts[2] == "cgo" { 92 bc.CgoEnabled = true 93 } else { 94 log.Fatalf("bad context: %q", c) 95 } 96 } 97 return bc 98 } 99 100 func setContexts() { 101 contexts = []*build.Context{} 102 for _, c := range strings.Split(*forceCtx, ",") { 103 contexts = append(contexts, parseContext(c)) 104 } 105 } 106 107 var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`) 108 109 func main() { 110 flag.Parse() 111 112 if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") { 113 if *nextFile != "" { 114 fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile) 115 *nextFile = "" 116 } 117 } 118 119 if *forceCtx != "" { 120 setContexts() 121 } 122 for _, c := range contexts { 123 c.Compiler = build.Default.Compiler 124 } 125 126 var pkgNames []string 127 if flag.NArg() > 0 { 128 pkgNames = flag.Args() 129 } else { 130 stds, err := exec.Command("go", "list", "std").Output() 131 if err != nil { 132 log.Fatal(err) 133 } 134 for _, pkg := range strings.Fields(string(stds)) { 135 if !internalPkg.MatchString(pkg) { 136 pkgNames = append(pkgNames, pkg) 137 } 138 } 139 } 140 141 var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true 142 for _, context := range contexts { 143 w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src")) 144 145 for _, name := range pkgNames { 146 // Vendored packages do not contribute to our 147 // public API surface. 148 if strings.HasPrefix(name, "vendor/") { 149 continue 150 } 151 // - Package "unsafe" contains special signatures requiring 152 // extra care when printing them - ignore since it is not 153 // going to change w/o a language change. 154 // - We don't care about the API of commands. 155 if name != "unsafe" && !strings.HasPrefix(name, "cmd/") { 156 if name == "runtime/cgo" && !context.CgoEnabled { 157 // w.Import(name) will return nil 158 continue 159 } 160 pkg, _ := w.Import(name) 161 w.export(pkg) 162 } 163 } 164 165 ctxName := contextName(context) 166 for _, f := range w.Features() { 167 if featureCtx[f] == nil { 168 featureCtx[f] = make(map[string]bool) 169 } 170 featureCtx[f][ctxName] = true 171 } 172 } 173 174 var features []string 175 for f, cmap := range featureCtx { 176 if len(cmap) == len(contexts) { 177 features = append(features, f) 178 continue 179 } 180 comma := strings.Index(f, ",") 181 for cname := range cmap { 182 f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:]) 183 features = append(features, f2) 184 } 185 } 186 187 fail := false 188 defer func() { 189 if fail { 190 os.Exit(1) 191 } 192 }() 193 194 bw := bufio.NewWriter(os.Stdout) 195 defer bw.Flush() 196 197 if *checkFile == "" { 198 sort.Strings(features) 199 for _, f := range features { 200 fmt.Fprintln(bw, f) 201 } 202 return 203 } 204 205 var required []string 206 for _, file := range strings.Split(*checkFile, ",") { 207 required = append(required, fileFeatures(file)...) 208 } 209 optional := fileFeatures(*nextFile) 210 exception := fileFeatures(*exceptFile) 211 fail = !compareAPI(bw, features, required, optional, exception, 212 *allowNew && strings.Contains(runtime.Version(), "devel")) 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, allowAdd bool) (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 !allowAdd { 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 if f := parsedFileCache[filename]; f != nil { 364 return f, nil 365 } 366 367 f, err := parser.ParseFile(fset, filename, nil, 0) 368 if err != nil { 369 return nil, err 370 } 371 parsedFileCache[filename] = f 372 373 return f, nil 374 } 375 376 // The package cache doesn't operate correctly in rare (so far artificial) 377 // circumstances (issue 8425). Disable before debugging non-obvious errors 378 // from the type-checker. 379 const usePkgCache = true 380 381 var ( 382 pkgCache = map[string]*types.Package{} // map tagKey to package 383 pkgTags = map[string][]string{} // map import dir to list of relevant tags 384 ) 385 386 // tagKey returns the tag-based key to use in the pkgCache. 387 // It is a comma-separated string; the first part is dir, the rest tags. 388 // The satisfied tags are derived from context but only those that 389 // matter (the ones listed in the tags argument) are used. 390 // The tags list, which came from go/build's Package.AllTags, 391 // is known to be sorted. 392 func tagKey(dir string, context *build.Context, tags []string) string { 393 ctags := map[string]bool{ 394 context.GOOS: true, 395 context.GOARCH: true, 396 } 397 if context.CgoEnabled { 398 ctags["cgo"] = true 399 } 400 for _, tag := range context.BuildTags { 401 ctags[tag] = true 402 } 403 // TODO: ReleaseTags (need to load default) 404 key := dir 405 for _, tag := range tags { 406 if ctags[tag] { 407 key += "," + tag 408 } 409 } 410 return key 411 } 412 413 // Importing is a sentinel taking the place in Walker.imported 414 // for a package that is in the process of being imported. 415 var importing types.Package 416 417 func (w *Walker) Import(name string) (*types.Package, error) { 418 pkg := w.imported[name] 419 if pkg != nil { 420 if pkg == &importing { 421 log.Fatalf("cycle importing package %q", name) 422 } 423 return pkg, nil 424 } 425 w.imported[name] = &importing 426 427 root := w.root 428 if strings.HasPrefix(name, "golang_org/x/") { 429 root = filepath.Join(root, "vendor") 430 } 431 432 // Determine package files. 433 dir := filepath.Join(root, filepath.FromSlash(name)) 434 if fi, err := os.Stat(dir); err != nil || !fi.IsDir() { 435 log.Fatalf("no source in tree for import %q: %v", name, err) 436 } 437 438 context := w.context 439 if context == nil { 440 context = &build.Default 441 } 442 443 // Look in cache. 444 // If we've already done an import with the same set 445 // of relevant tags, reuse the result. 446 var key string 447 if usePkgCache { 448 if tags, ok := pkgTags[dir]; ok { 449 key = tagKey(dir, context, tags) 450 if pkg := pkgCache[key]; pkg != nil { 451 w.imported[name] = pkg 452 return pkg, nil 453 } 454 } 455 } 456 457 info, err := context.ImportDir(dir, 0) 458 if err != nil { 459 if _, nogo := err.(*build.NoGoError); nogo { 460 return nil, nil 461 } 462 log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) 463 } 464 465 // Save tags list first time we see a directory. 466 if usePkgCache { 467 if _, ok := pkgTags[dir]; !ok { 468 pkgTags[dir] = info.AllTags 469 key = tagKey(dir, context, info.AllTags) 470 } 471 } 472 473 filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...) 474 475 // Parse package files. 476 var files []*ast.File 477 for _, file := range filenames { 478 f, err := w.parseFile(dir, file) 479 if err != nil { 480 log.Fatalf("error parsing package %s: %s", name, err) 481 } 482 files = append(files, f) 483 } 484 485 // Type-check package files. 486 conf := types.Config{ 487 IgnoreFuncBodies: true, 488 FakeImportC: true, 489 Importer: w, 490 } 491 pkg, err = conf.Check(name, fset, files, nil) 492 if err != nil { 493 ctxt := "<no context>" 494 if w.context != nil { 495 ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH) 496 } 497 log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt) 498 } 499 500 if usePkgCache { 501 pkgCache[key] = pkg 502 } 503 504 w.imported[name] = pkg 505 return pkg, nil 506 } 507 508 // pushScope enters a new scope (walking a package, type, node, etc) 509 // and returns a function that will leave the scope (with sanity checking 510 // for mismatched pushes & pops) 511 func (w *Walker) pushScope(name string) (popFunc func()) { 512 w.scope = append(w.scope, name) 513 return func() { 514 if len(w.scope) == 0 { 515 log.Fatalf("attempt to leave scope %q with empty scope list", name) 516 } 517 if w.scope[len(w.scope)-1] != name { 518 log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope) 519 } 520 w.scope = w.scope[:len(w.scope)-1] 521 } 522 } 523 524 func sortedMethodNames(typ *types.Interface) []string { 525 n := typ.NumMethods() 526 list := make([]string, n) 527 for i := range list { 528 list[i] = typ.Method(i).Name() 529 } 530 sort.Strings(list) 531 return list 532 } 533 534 func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) { 535 switch typ := typ.(type) { 536 case *types.Basic: 537 s := typ.Name() 538 switch typ.Kind() { 539 case types.UnsafePointer: 540 s = "unsafe.Pointer" 541 case types.UntypedBool: 542 s = "ideal-bool" 543 case types.UntypedInt: 544 s = "ideal-int" 545 case types.UntypedRune: 546 // "ideal-char" for compatibility with old tool 547 // TODO(gri) change to "ideal-rune" 548 s = "ideal-char" 549 case types.UntypedFloat: 550 s = "ideal-float" 551 case types.UntypedComplex: 552 s = "ideal-complex" 553 case types.UntypedString: 554 s = "ideal-string" 555 case types.UntypedNil: 556 panic("should never see untyped nil type") 557 default: 558 switch s { 559 case "byte": 560 s = "uint8" 561 case "rune": 562 s = "int32" 563 } 564 } 565 buf.WriteString(s) 566 567 case *types.Array: 568 fmt.Fprintf(buf, "[%d]", typ.Len()) 569 w.writeType(buf, typ.Elem()) 570 571 case *types.Slice: 572 buf.WriteString("[]") 573 w.writeType(buf, typ.Elem()) 574 575 case *types.Struct: 576 buf.WriteString("struct") 577 578 case *types.Pointer: 579 buf.WriteByte('*') 580 w.writeType(buf, typ.Elem()) 581 582 case *types.Tuple: 583 panic("should never see a tuple type") 584 585 case *types.Signature: 586 buf.WriteString("func") 587 w.writeSignature(buf, typ) 588 589 case *types.Interface: 590 buf.WriteString("interface{") 591 if typ.NumMethods() > 0 { 592 buf.WriteByte(' ') 593 buf.WriteString(strings.Join(sortedMethodNames(typ), ", ")) 594 buf.WriteByte(' ') 595 } 596 buf.WriteString("}") 597 598 case *types.Map: 599 buf.WriteString("map[") 600 w.writeType(buf, typ.Key()) 601 buf.WriteByte(']') 602 w.writeType(buf, typ.Elem()) 603 604 case *types.Chan: 605 var s string 606 switch typ.Dir() { 607 case types.SendOnly: 608 s = "chan<- " 609 case types.RecvOnly: 610 s = "<-chan " 611 case types.SendRecv: 612 s = "chan " 613 default: 614 panic("unreachable") 615 } 616 buf.WriteString(s) 617 w.writeType(buf, typ.Elem()) 618 619 case *types.Named: 620 obj := typ.Obj() 621 pkg := obj.Pkg() 622 if pkg != nil && pkg != w.current { 623 buf.WriteString(pkg.Name()) 624 buf.WriteByte('.') 625 } 626 buf.WriteString(typ.Obj().Name()) 627 628 default: 629 panic(fmt.Sprintf("unknown type %T", typ)) 630 } 631 } 632 633 func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) { 634 w.writeParams(buf, sig.Params(), sig.Variadic()) 635 switch res := sig.Results(); res.Len() { 636 case 0: 637 // nothing to do 638 case 1: 639 buf.WriteByte(' ') 640 w.writeType(buf, res.At(0).Type()) 641 default: 642 buf.WriteByte(' ') 643 w.writeParams(buf, res, false) 644 } 645 } 646 647 func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) { 648 buf.WriteByte('(') 649 for i, n := 0, t.Len(); i < n; i++ { 650 if i > 0 { 651 buf.WriteString(", ") 652 } 653 typ := t.At(i).Type() 654 if variadic && i+1 == n { 655 buf.WriteString("...") 656 typ = typ.(*types.Slice).Elem() 657 } 658 w.writeType(buf, typ) 659 } 660 buf.WriteByte(')') 661 } 662 663 func (w *Walker) typeString(typ types.Type) string { 664 var buf bytes.Buffer 665 w.writeType(&buf, typ) 666 return buf.String() 667 } 668 669 func (w *Walker) signatureString(sig *types.Signature) string { 670 var buf bytes.Buffer 671 w.writeSignature(&buf, sig) 672 return buf.String() 673 } 674 675 func (w *Walker) emitObj(obj types.Object) { 676 switch obj := obj.(type) { 677 case *types.Const: 678 w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type())) 679 x := obj.Val() 680 short := x.String() 681 exact := x.ExactString() 682 if short == exact { 683 w.emitf("const %s = %s", obj.Name(), short) 684 } else { 685 w.emitf("const %s = %s // %s", obj.Name(), short, exact) 686 } 687 case *types.Var: 688 w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type())) 689 case *types.TypeName: 690 w.emitType(obj) 691 case *types.Func: 692 w.emitFunc(obj) 693 default: 694 panic("unknown object: " + obj.String()) 695 } 696 } 697 698 func (w *Walker) emitType(obj *types.TypeName) { 699 name := obj.Name() 700 typ := obj.Type() 701 switch typ := typ.Underlying().(type) { 702 case *types.Struct: 703 w.emitStructType(name, typ) 704 case *types.Interface: 705 w.emitIfaceType(name, typ) 706 return // methods are handled by emitIfaceType 707 default: 708 w.emitf("type %s %s", name, w.typeString(typ.Underlying())) 709 } 710 711 // emit methods with value receiver 712 var methodNames map[string]bool 713 vset := types.NewMethodSet(typ) 714 for i, n := 0, vset.Len(); i < n; i++ { 715 m := vset.At(i) 716 if m.Obj().Exported() { 717 w.emitMethod(m) 718 if methodNames == nil { 719 methodNames = make(map[string]bool) 720 } 721 methodNames[m.Obj().Name()] = true 722 } 723 } 724 725 // emit methods with pointer receiver; exclude 726 // methods that we have emitted already 727 // (the method set of *T includes the methods of T) 728 pset := types.NewMethodSet(types.NewPointer(typ)) 729 for i, n := 0, pset.Len(); i < n; i++ { 730 m := pset.At(i) 731 if m.Obj().Exported() && !methodNames[m.Obj().Name()] { 732 w.emitMethod(m) 733 } 734 } 735 } 736 737 func (w *Walker) emitStructType(name string, typ *types.Struct) { 738 typeStruct := fmt.Sprintf("type %s struct", name) 739 w.emitf(typeStruct) 740 defer w.pushScope(typeStruct)() 741 742 for i := 0; i < typ.NumFields(); i++ { 743 f := typ.Field(i) 744 if !f.Exported() { 745 continue 746 } 747 typ := f.Type() 748 if f.Anonymous() { 749 w.emitf("embedded %s", w.typeString(typ)) 750 continue 751 } 752 w.emitf("%s %s", f.Name(), w.typeString(typ)) 753 } 754 } 755 756 func (w *Walker) emitIfaceType(name string, typ *types.Interface) { 757 pop := w.pushScope("type " + name + " interface") 758 759 var methodNames []string 760 complete := true 761 mset := types.NewMethodSet(typ) 762 for i, n := 0, mset.Len(); i < n; i++ { 763 m := mset.At(i).Obj().(*types.Func) 764 if !m.Exported() { 765 complete = false 766 continue 767 } 768 methodNames = append(methodNames, m.Name()) 769 w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature))) 770 } 771 772 if !complete { 773 // The method set has unexported methods, so all the 774 // implementations are provided by the same package, 775 // so the method set can be extended. Instead of recording 776 // the full set of names (below), record only that there were 777 // unexported methods. (If the interface shrinks, we will notice 778 // because a method signature emitted during the last loop 779 // will disappear.) 780 w.emitf("unexported methods") 781 } 782 783 pop() 784 785 if !complete { 786 return 787 } 788 789 if len(methodNames) == 0 { 790 w.emitf("type %s interface {}", name) 791 return 792 } 793 794 sort.Strings(methodNames) 795 w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", ")) 796 } 797 798 func (w *Walker) emitFunc(f *types.Func) { 799 sig := f.Type().(*types.Signature) 800 if sig.Recv() != nil { 801 panic("method considered a regular function: " + f.String()) 802 } 803 w.emitf("func %s%s", f.Name(), w.signatureString(sig)) 804 } 805 806 func (w *Walker) emitMethod(m *types.Selection) { 807 sig := m.Type().(*types.Signature) 808 recv := sig.Recv().Type() 809 // report exported methods with unexported receiver base type 810 if true { 811 base := recv 812 if p, _ := recv.(*types.Pointer); p != nil { 813 base = p.Elem() 814 } 815 if obj := base.(*types.Named).Obj(); !obj.Exported() { 816 log.Fatalf("exported method with unexported receiver base type: %s", m) 817 } 818 } 819 w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig)) 820 } 821 822 func (w *Walker) emitf(format string, args ...interface{}) { 823 f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...) 824 if strings.Contains(f, "\n") { 825 panic("feature contains newlines: " + f) 826 } 827 828 if _, dup := w.features[f]; dup { 829 panic("duplicate feature inserted: " + f) 830 } 831 w.features[f] = true 832 833 if *verbose { 834 log.Printf("feature: %s", f) 835 } 836 }