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