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