github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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  			// - 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  // The package cache doesn't operate correctly in rare (so far artificial)
   372  // circumstances (issue 8425). Disable before debugging non-obvious errors
   373  // from the type-checker.
   374  const usePkgCache = true
   375  
   376  var (
   377  	pkgCache = map[string]*types.Package{} // map tagKey to package
   378  	pkgTags  = map[string][]string{}       // map import dir to list of relevant tags
   379  )
   380  
   381  // tagKey returns the tag-based key to use in the pkgCache.
   382  // It is a comma-separated string; the first part is dir, the rest tags.
   383  // The satisfied tags are derived from context but only those that
   384  // matter (the ones listed in the tags argument) are used.
   385  // The tags list, which came from go/build's Package.AllTags,
   386  // is known to be sorted.
   387  func tagKey(dir string, context *build.Context, tags []string) string {
   388  	ctags := map[string]bool{
   389  		context.GOOS:   true,
   390  		context.GOARCH: true,
   391  	}
   392  	if context.CgoEnabled {
   393  		ctags["cgo"] = true
   394  	}
   395  	for _, tag := range context.BuildTags {
   396  		ctags[tag] = true
   397  	}
   398  	// TODO: ReleaseTags (need to load default)
   399  	key := dir
   400  	for _, tag := range tags {
   401  		if ctags[tag] {
   402  			key += "," + tag
   403  		}
   404  	}
   405  	return key
   406  }
   407  
   408  // Importing is a sentinel taking the place in Walker.imported
   409  // for a package that is in the process of being imported.
   410  var importing types.Package
   411  
   412  func (w *Walker) Import(name string) (*types.Package, error) {
   413  	pkg := w.imported[name]
   414  	if pkg != nil {
   415  		if pkg == &importing {
   416  			log.Fatalf("cycle importing package %q", name)
   417  		}
   418  		return pkg, nil
   419  	}
   420  	w.imported[name] = &importing
   421  
   422  	root := w.root
   423  	if strings.HasPrefix(name, "golang.org/x/") {
   424  		root = filepath.Join(root, "vendor")
   425  	}
   426  
   427  	// Determine package files.
   428  	dir := filepath.Join(root, filepath.FromSlash(name))
   429  	if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
   430  		log.Fatalf("no source in tree for import %q: %v", name, err)
   431  	}
   432  
   433  	context := w.context
   434  	if context == nil {
   435  		context = &build.Default
   436  	}
   437  
   438  	// Look in cache.
   439  	// If we've already done an import with the same set
   440  	// of relevant tags, reuse the result.
   441  	var key string
   442  	if usePkgCache {
   443  		if tags, ok := pkgTags[dir]; ok {
   444  			key = tagKey(dir, context, tags)
   445  			if pkg := pkgCache[key]; pkg != nil {
   446  				w.imported[name] = pkg
   447  				return pkg, nil
   448  			}
   449  		}
   450  	}
   451  
   452  	info, err := context.ImportDir(dir, 0)
   453  	if err != nil {
   454  		if _, nogo := err.(*build.NoGoError); nogo {
   455  			return nil, nil
   456  		}
   457  		log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
   458  	}
   459  
   460  	// Save tags list first time we see a directory.
   461  	if usePkgCache {
   462  		if _, ok := pkgTags[dir]; !ok {
   463  			pkgTags[dir] = info.AllTags
   464  			key = tagKey(dir, context, info.AllTags)
   465  		}
   466  	}
   467  
   468  	filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
   469  
   470  	// Parse package files.
   471  	var files []*ast.File
   472  	for _, file := range filenames {
   473  		f, err := w.parseFile(dir, file)
   474  		if err != nil {
   475  			log.Fatalf("error parsing package %s: %s", name, err)
   476  		}
   477  		files = append(files, f)
   478  	}
   479  
   480  	// Type-check package files.
   481  	conf := types.Config{
   482  		IgnoreFuncBodies: true,
   483  		FakeImportC:      true,
   484  		Importer:         w,
   485  	}
   486  	pkg, err = conf.Check(name, fset, files, nil)
   487  	if err != nil {
   488  		ctxt := "<no context>"
   489  		if w.context != nil {
   490  			ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
   491  		}
   492  		log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
   493  	}
   494  
   495  	if usePkgCache {
   496  		pkgCache[key] = pkg
   497  	}
   498  
   499  	w.imported[name] = pkg
   500  	return pkg, nil
   501  }
   502  
   503  // pushScope enters a new scope (walking a package, type, node, etc)
   504  // and returns a function that will leave the scope (with sanity checking
   505  // for mismatched pushes & pops)
   506  func (w *Walker) pushScope(name string) (popFunc func()) {
   507  	w.scope = append(w.scope, name)
   508  	return func() {
   509  		if len(w.scope) == 0 {
   510  			log.Fatalf("attempt to leave scope %q with empty scope list", name)
   511  		}
   512  		if w.scope[len(w.scope)-1] != name {
   513  			log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
   514  		}
   515  		w.scope = w.scope[:len(w.scope)-1]
   516  	}
   517  }
   518  
   519  func sortedMethodNames(typ *types.Interface) []string {
   520  	n := typ.NumMethods()
   521  	list := make([]string, n)
   522  	for i := range list {
   523  		list[i] = typ.Method(i).Name()
   524  	}
   525  	sort.Strings(list)
   526  	return list
   527  }
   528  
   529  func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
   530  	switch typ := typ.(type) {
   531  	case *types.Basic:
   532  		s := typ.Name()
   533  		switch typ.Kind() {
   534  		case types.UnsafePointer:
   535  			s = "unsafe.Pointer"
   536  		case types.UntypedBool:
   537  			s = "ideal-bool"
   538  		case types.UntypedInt:
   539  			s = "ideal-int"
   540  		case types.UntypedRune:
   541  			// "ideal-char" for compatibility with old tool
   542  			// TODO(gri) change to "ideal-rune"
   543  			s = "ideal-char"
   544  		case types.UntypedFloat:
   545  			s = "ideal-float"
   546  		case types.UntypedComplex:
   547  			s = "ideal-complex"
   548  		case types.UntypedString:
   549  			s = "ideal-string"
   550  		case types.UntypedNil:
   551  			panic("should never see untyped nil type")
   552  		default:
   553  			switch s {
   554  			case "byte":
   555  				s = "uint8"
   556  			case "rune":
   557  				s = "int32"
   558  			}
   559  		}
   560  		buf.WriteString(s)
   561  
   562  	case *types.Array:
   563  		fmt.Fprintf(buf, "[%d]", typ.Len())
   564  		w.writeType(buf, typ.Elem())
   565  
   566  	case *types.Slice:
   567  		buf.WriteString("[]")
   568  		w.writeType(buf, typ.Elem())
   569  
   570  	case *types.Struct:
   571  		buf.WriteString("struct")
   572  
   573  	case *types.Pointer:
   574  		buf.WriteByte('*')
   575  		w.writeType(buf, typ.Elem())
   576  
   577  	case *types.Tuple:
   578  		panic("should never see a tuple type")
   579  
   580  	case *types.Signature:
   581  		buf.WriteString("func")
   582  		w.writeSignature(buf, typ)
   583  
   584  	case *types.Interface:
   585  		buf.WriteString("interface{")
   586  		if typ.NumMethods() > 0 {
   587  			buf.WriteByte(' ')
   588  			buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
   589  			buf.WriteByte(' ')
   590  		}
   591  		buf.WriteString("}")
   592  
   593  	case *types.Map:
   594  		buf.WriteString("map[")
   595  		w.writeType(buf, typ.Key())
   596  		buf.WriteByte(']')
   597  		w.writeType(buf, typ.Elem())
   598  
   599  	case *types.Chan:
   600  		var s string
   601  		switch typ.Dir() {
   602  		case types.SendOnly:
   603  			s = "chan<- "
   604  		case types.RecvOnly:
   605  			s = "<-chan "
   606  		case types.SendRecv:
   607  			s = "chan "
   608  		default:
   609  			panic("unreachable")
   610  		}
   611  		buf.WriteString(s)
   612  		w.writeType(buf, typ.Elem())
   613  
   614  	case *types.Named:
   615  		obj := typ.Obj()
   616  		pkg := obj.Pkg()
   617  		if pkg != nil && pkg != w.current {
   618  			buf.WriteString(pkg.Name())
   619  			buf.WriteByte('.')
   620  		}
   621  		buf.WriteString(typ.Obj().Name())
   622  
   623  	default:
   624  		panic(fmt.Sprintf("unknown type %T", typ))
   625  	}
   626  }
   627  
   628  func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
   629  	w.writeParams(buf, sig.Params(), sig.Variadic())
   630  	switch res := sig.Results(); res.Len() {
   631  	case 0:
   632  		// nothing to do
   633  	case 1:
   634  		buf.WriteByte(' ')
   635  		w.writeType(buf, res.At(0).Type())
   636  	default:
   637  		buf.WriteByte(' ')
   638  		w.writeParams(buf, res, false)
   639  	}
   640  }
   641  
   642  func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
   643  	buf.WriteByte('(')
   644  	for i, n := 0, t.Len(); i < n; i++ {
   645  		if i > 0 {
   646  			buf.WriteString(", ")
   647  		}
   648  		typ := t.At(i).Type()
   649  		if variadic && i+1 == n {
   650  			buf.WriteString("...")
   651  			typ = typ.(*types.Slice).Elem()
   652  		}
   653  		w.writeType(buf, typ)
   654  	}
   655  	buf.WriteByte(')')
   656  }
   657  
   658  func (w *Walker) typeString(typ types.Type) string {
   659  	var buf bytes.Buffer
   660  	w.writeType(&buf, typ)
   661  	return buf.String()
   662  }
   663  
   664  func (w *Walker) signatureString(sig *types.Signature) string {
   665  	var buf bytes.Buffer
   666  	w.writeSignature(&buf, sig)
   667  	return buf.String()
   668  }
   669  
   670  func (w *Walker) emitObj(obj types.Object) {
   671  	switch obj := obj.(type) {
   672  	case *types.Const:
   673  		w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
   674  		x := obj.Val()
   675  		short := x.String()
   676  		exact := x.ExactString()
   677  		if short == exact {
   678  			w.emitf("const %s = %s", obj.Name(), short)
   679  		} else {
   680  			w.emitf("const %s = %s  // %s", obj.Name(), short, exact)
   681  		}
   682  	case *types.Var:
   683  		w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
   684  	case *types.TypeName:
   685  		w.emitType(obj)
   686  	case *types.Func:
   687  		w.emitFunc(obj)
   688  	default:
   689  		panic("unknown object: " + obj.String())
   690  	}
   691  }
   692  
   693  func (w *Walker) emitType(obj *types.TypeName) {
   694  	name := obj.Name()
   695  	typ := obj.Type()
   696  	switch typ := typ.Underlying().(type) {
   697  	case *types.Struct:
   698  		w.emitStructType(name, typ)
   699  	case *types.Interface:
   700  		w.emitIfaceType(name, typ)
   701  		return // methods are handled by emitIfaceType
   702  	default:
   703  		w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
   704  	}
   705  
   706  	// emit methods with value receiver
   707  	var methodNames map[string]bool
   708  	vset := types.NewMethodSet(typ)
   709  	for i, n := 0, vset.Len(); i < n; i++ {
   710  		m := vset.At(i)
   711  		if m.Obj().Exported() {
   712  			w.emitMethod(m)
   713  			if methodNames == nil {
   714  				methodNames = make(map[string]bool)
   715  			}
   716  			methodNames[m.Obj().Name()] = true
   717  		}
   718  	}
   719  
   720  	// emit methods with pointer receiver; exclude
   721  	// methods that we have emitted already
   722  	// (the method set of *T includes the methods of T)
   723  	pset := types.NewMethodSet(types.NewPointer(typ))
   724  	for i, n := 0, pset.Len(); i < n; i++ {
   725  		m := pset.At(i)
   726  		if m.Obj().Exported() && !methodNames[m.Obj().Name()] {
   727  			w.emitMethod(m)
   728  		}
   729  	}
   730  }
   731  
   732  func (w *Walker) emitStructType(name string, typ *types.Struct) {
   733  	typeStruct := fmt.Sprintf("type %s struct", name)
   734  	w.emitf(typeStruct)
   735  	defer w.pushScope(typeStruct)()
   736  
   737  	for i := 0; i < typ.NumFields(); i++ {
   738  		f := typ.Field(i)
   739  		if !f.Exported() {
   740  			continue
   741  		}
   742  		typ := f.Type()
   743  		if f.Anonymous() {
   744  			w.emitf("embedded %s", w.typeString(typ))
   745  			continue
   746  		}
   747  		w.emitf("%s %s", f.Name(), w.typeString(typ))
   748  	}
   749  }
   750  
   751  func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
   752  	pop := w.pushScope("type " + name + " interface")
   753  
   754  	var methodNames []string
   755  	complete := true
   756  	mset := types.NewMethodSet(typ)
   757  	for i, n := 0, mset.Len(); i < n; i++ {
   758  		m := mset.At(i).Obj().(*types.Func)
   759  		if !m.Exported() {
   760  			complete = false
   761  			continue
   762  		}
   763  		methodNames = append(methodNames, m.Name())
   764  		w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
   765  	}
   766  
   767  	if !complete {
   768  		// The method set has unexported methods, so all the
   769  		// implementations are provided by the same package,
   770  		// so the method set can be extended. Instead of recording
   771  		// the full set of names (below), record only that there were
   772  		// unexported methods. (If the interface shrinks, we will notice
   773  		// because a method signature emitted during the last loop
   774  		// will disappear.)
   775  		w.emitf("unexported methods")
   776  	}
   777  
   778  	pop()
   779  
   780  	if !complete {
   781  		return
   782  	}
   783  
   784  	if len(methodNames) == 0 {
   785  		w.emitf("type %s interface {}", name)
   786  		return
   787  	}
   788  
   789  	sort.Strings(methodNames)
   790  	w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
   791  }
   792  
   793  func (w *Walker) emitFunc(f *types.Func) {
   794  	sig := f.Type().(*types.Signature)
   795  	if sig.Recv() != nil {
   796  		panic("method considered a regular function: " + f.String())
   797  	}
   798  	w.emitf("func %s%s", f.Name(), w.signatureString(sig))
   799  }
   800  
   801  func (w *Walker) emitMethod(m *types.Selection) {
   802  	sig := m.Type().(*types.Signature)
   803  	recv := sig.Recv().Type()
   804  	// report exported methods with unexported receiver base type
   805  	if true {
   806  		base := recv
   807  		if p, _ := recv.(*types.Pointer); p != nil {
   808  			base = p.Elem()
   809  		}
   810  		if obj := base.(*types.Named).Obj(); !obj.Exported() {
   811  			log.Fatalf("exported method with unexported receiver base type: %s", m)
   812  		}
   813  	}
   814  	w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig))
   815  }
   816  
   817  func (w *Walker) emitf(format string, args ...interface{}) {
   818  	f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
   819  	if strings.Contains(f, "\n") {
   820  		panic("feature contains newlines: " + f)
   821  	}
   822  
   823  	if _, dup := w.features[f]; dup {
   824  		panic("duplicate feature inserted: " + f)
   825  	}
   826  	w.features[f] = true
   827  
   828  	if *verbose {
   829  		log.Printf("feature: %s", f)
   830  	}
   831  }