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  }