github.com/akaros/go-akaros@v0.0.0-20181004170632-85005d477eab/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: "akaros", GOARCH: "386", CgoEnabled: true},
    53  	{GOOS: "akaros", GOARCH: "386"},
    54  	{GOOS: "akaros", GOARCH: "amd64", CgoEnabled: true},
    55  	{GOOS: "akaros", GOARCH: "amd64"},
    56  	{GOOS: "darwin", GOARCH: "386", CgoEnabled: true},
    57  	{GOOS: "darwin", GOARCH: "386"},
    58  	{GOOS: "darwin", GOARCH: "amd64", CgoEnabled: true},
    59  	{GOOS: "darwin", GOARCH: "amd64"},
    60  	{GOOS: "windows", GOARCH: "amd64"},
    61  	{GOOS: "windows", GOARCH: "386"},
    62  	{GOOS: "freebsd", GOARCH: "386", CgoEnabled: true},
    63  	{GOOS: "freebsd", GOARCH: "386"},
    64  	{GOOS: "freebsd", GOARCH: "amd64", CgoEnabled: true},
    65  	{GOOS: "freebsd", GOARCH: "amd64"},
    66  	{GOOS: "freebsd", GOARCH: "arm", CgoEnabled: true},
    67  	{GOOS: "freebsd", GOARCH: "arm"},
    68  	{GOOS: "netbsd", GOARCH: "386", CgoEnabled: true},
    69  	{GOOS: "netbsd", GOARCH: "386"},
    70  	{GOOS: "netbsd", GOARCH: "amd64", CgoEnabled: true},
    71  	{GOOS: "netbsd", GOARCH: "amd64"},
    72  	{GOOS: "netbsd", GOARCH: "arm", CgoEnabled: true},
    73  	{GOOS: "netbsd", GOARCH: "arm"},
    74  	{GOOS: "openbsd", GOARCH: "386", CgoEnabled: true},
    75  	{GOOS: "openbsd", GOARCH: "386"},
    76  	{GOOS: "openbsd", GOARCH: "amd64", CgoEnabled: true},
    77  	{GOOS: "openbsd", GOARCH: "amd64"},
    78  }
    79  
    80  func contextName(c *build.Context) string {
    81  	s := c.GOOS + "-" + c.GOARCH
    82  	if c.CgoEnabled {
    83  		return s + "-cgo"
    84  	}
    85  	return s
    86  }
    87  
    88  func parseContext(c string) *build.Context {
    89  	parts := strings.Split(c, "-")
    90  	if len(parts) < 2 {
    91  		log.Fatalf("bad context: %q", c)
    92  	}
    93  	bc := &build.Context{
    94  		GOOS:   parts[0],
    95  		GOARCH: parts[1],
    96  	}
    97  	if len(parts) == 3 {
    98  		if parts[2] == "cgo" {
    99  			bc.CgoEnabled = true
   100  		} else {
   101  			log.Fatalf("bad context: %q", c)
   102  		}
   103  	}
   104  	return bc
   105  }
   106  
   107  func setContexts() {
   108  	contexts = []*build.Context{}
   109  	for _, c := range strings.Split(*forceCtx, ",") {
   110  		contexts = append(contexts, parseContext(c))
   111  	}
   112  }
   113  
   114  var internalPkg = regexp.MustCompile(`(^|/)internal($|/)`)
   115  
   116  func main() {
   117  	flag.Parse()
   118  
   119  	if !strings.Contains(runtime.Version(), "weekly") && !strings.Contains(runtime.Version(), "devel") {
   120  		if *nextFile != "" {
   121  			fmt.Printf("Go version is %q, ignoring -next %s\n", runtime.Version(), *nextFile)
   122  			*nextFile = ""
   123  		}
   124  	}
   125  
   126  	if *forceCtx != "" {
   127  		setContexts()
   128  	}
   129  	for _, c := range contexts {
   130  		c.Compiler = build.Default.Compiler
   131  	}
   132  
   133  	var pkgNames []string
   134  	if flag.NArg() > 0 {
   135  		pkgNames = flag.Args()
   136  	} else {
   137  		stds, err := exec.Command("go", "list", "std").Output()
   138  		if err != nil {
   139  			log.Fatal(err)
   140  		}
   141  		for _, pkg := range strings.Fields(string(stds)) {
   142  			if !internalPkg.MatchString(pkg) {
   143  				pkgNames = append(pkgNames, pkg)
   144  			}
   145  		}
   146  	}
   147  
   148  	var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
   149  	for _, context := range contexts {
   150  		w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src"))
   151  
   152  		for _, name := range pkgNames {
   153  			// - Package "unsafe" contains special signatures requiring
   154  			//   extra care when printing them - ignore since it is not
   155  			//   going to change w/o a language change.
   156  			// - We don't care about the API of commands.
   157  			if name != "unsafe" && !strings.HasPrefix(name, "cmd/") {
   158  				if name == "runtime/cgo" && !context.CgoEnabled {
   159  					// w.Import(name) will return nil
   160  					continue
   161  				}
   162  				w.export(w.Import(name))
   163  			}
   164  		}
   165  
   166  		ctxName := contextName(context)
   167  		for _, f := range w.Features() {
   168  			if featureCtx[f] == nil {
   169  				featureCtx[f] = make(map[string]bool)
   170  			}
   171  			featureCtx[f][ctxName] = true
   172  		}
   173  	}
   174  
   175  	var features []string
   176  	for f, cmap := range featureCtx {
   177  		if len(cmap) == len(contexts) {
   178  			features = append(features, f)
   179  			continue
   180  		}
   181  		comma := strings.Index(f, ",")
   182  		for cname := range cmap {
   183  			f2 := fmt.Sprintf("%s (%s)%s", f[:comma], cname, f[comma:])
   184  			features = append(features, f2)
   185  		}
   186  	}
   187  
   188  	fail := false
   189  	defer func() {
   190  		if fail {
   191  			os.Exit(1)
   192  		}
   193  	}()
   194  
   195  	bw := bufio.NewWriter(os.Stdout)
   196  	defer bw.Flush()
   197  
   198  	if *checkFile == "" {
   199  		sort.Strings(features)
   200  		for _, f := range features {
   201  			fmt.Fprintln(bw, f)
   202  		}
   203  		return
   204  	}
   205  
   206  	var required []string
   207  	for _, file := range strings.Split(*checkFile, ",") {
   208  		required = append(required, fileFeatures(file)...)
   209  	}
   210  	optional := fileFeatures(*nextFile)
   211  	exception := fileFeatures(*exceptFile)
   212  	fail = !compareAPI(bw, features, required, optional, exception)
   213  }
   214  
   215  // export emits the exported package features.
   216  func (w *Walker) export(pkg *types.Package) {
   217  	if *verbose {
   218  		log.Println(pkg)
   219  	}
   220  	pop := w.pushScope("pkg " + pkg.Path())
   221  	w.current = pkg
   222  	scope := pkg.Scope()
   223  	for _, name := range scope.Names() {
   224  		if ast.IsExported(name) {
   225  			w.emitObj(scope.Lookup(name))
   226  		}
   227  	}
   228  	pop()
   229  }
   230  
   231  func set(items []string) map[string]bool {
   232  	s := make(map[string]bool)
   233  	for _, v := range items {
   234  		s[v] = true
   235  	}
   236  	return s
   237  }
   238  
   239  var spaceParensRx = regexp.MustCompile(` \(\S+?\)`)
   240  
   241  func featureWithoutContext(f string) string {
   242  	if !strings.Contains(f, "(") {
   243  		return f
   244  	}
   245  	return spaceParensRx.ReplaceAllString(f, "")
   246  }
   247  
   248  func compareAPI(w io.Writer, features, required, optional, exception []string) (ok bool) {
   249  	ok = true
   250  
   251  	optionalSet := set(optional)
   252  	exceptionSet := set(exception)
   253  	featureSet := set(features)
   254  
   255  	sort.Strings(features)
   256  	sort.Strings(required)
   257  
   258  	take := func(sl *[]string) string {
   259  		s := (*sl)[0]
   260  		*sl = (*sl)[1:]
   261  		return s
   262  	}
   263  
   264  	for len(required) > 0 || len(features) > 0 {
   265  		switch {
   266  		case len(features) == 0 || (len(required) > 0 && required[0] < features[0]):
   267  			feature := take(&required)
   268  			if exceptionSet[feature] {
   269  				// An "unfortunate" case: the feature was once
   270  				// included in the API (e.g. go1.txt), but was
   271  				// subsequently removed. These are already
   272  				// acknowledged by being in the file
   273  				// "api/except.txt". No need to print them out
   274  				// here.
   275  			} else if featureSet[featureWithoutContext(feature)] {
   276  				// okay.
   277  			} else {
   278  				fmt.Fprintf(w, "-%s\n", feature)
   279  				ok = false // broke compatibility
   280  			}
   281  		case len(required) == 0 || (len(features) > 0 && required[0] > features[0]):
   282  			newFeature := take(&features)
   283  			if optionalSet[newFeature] {
   284  				// Known added feature to the upcoming release.
   285  				// Delete it from the map so we can detect any upcoming features
   286  				// which were never seen.  (so we can clean up the nextFile)
   287  				delete(optionalSet, newFeature)
   288  			} else {
   289  				fmt.Fprintf(w, "+%s\n", newFeature)
   290  				if !*allowNew || !strings.Contains(runtime.Version(), "devel") {
   291  					ok = false // we're in lock-down mode for next release
   292  				}
   293  			}
   294  		default:
   295  			take(&required)
   296  			take(&features)
   297  		}
   298  	}
   299  
   300  	// In next file, but not in API.
   301  	var missing []string
   302  	for feature := range optionalSet {
   303  		missing = append(missing, feature)
   304  	}
   305  	sort.Strings(missing)
   306  	for _, feature := range missing {
   307  		fmt.Fprintf(w, "±%s\n", feature)
   308  	}
   309  	return
   310  }
   311  
   312  func fileFeatures(filename string) []string {
   313  	if filename == "" {
   314  		return nil
   315  	}
   316  	bs, err := ioutil.ReadFile(filename)
   317  	if err != nil {
   318  		log.Fatalf("Error reading file %s: %v", filename, err)
   319  	}
   320  	lines := strings.Split(string(bs), "\n")
   321  	var nonblank []string
   322  	for _, line := range lines {
   323  		line = strings.TrimSpace(line)
   324  		if line != "" && !strings.HasPrefix(line, "#") {
   325  			nonblank = append(nonblank, line)
   326  		}
   327  	}
   328  	return nonblank
   329  }
   330  
   331  var fset = token.NewFileSet()
   332  
   333  type Walker struct {
   334  	context  *build.Context
   335  	root     string
   336  	scope    []string
   337  	current  *types.Package
   338  	features map[string]bool           // set
   339  	imported map[string]*types.Package // packages already imported
   340  }
   341  
   342  func NewWalker(context *build.Context, root string) *Walker {
   343  	return &Walker{
   344  		context:  context,
   345  		root:     root,
   346  		features: map[string]bool{},
   347  		imported: map[string]*types.Package{"unsafe": types.Unsafe},
   348  	}
   349  }
   350  
   351  func (w *Walker) Features() (fs []string) {
   352  	for f := range w.features {
   353  		fs = append(fs, f)
   354  	}
   355  	sort.Strings(fs)
   356  	return
   357  }
   358  
   359  var parsedFileCache = make(map[string]*ast.File)
   360  
   361  func (w *Walker) parseFile(dir, file string) (*ast.File, error) {
   362  	filename := filepath.Join(dir, file)
   363  	f, _ := parsedFileCache[filename]
   364  	if f != nil {
   365  		return f, nil
   366  	}
   367  
   368  	var err error
   369  
   370  	// generate missing context-dependent files.
   371  
   372  	if w.context != nil && file == fmt.Sprintf("zgoos_%s.go", w.context.GOOS) {
   373  		src := fmt.Sprintf("package runtime; const theGoos = `%s`", w.context.GOOS)
   374  		f, err = parser.ParseFile(fset, filename, src, 0)
   375  		if err != nil {
   376  			log.Fatalf("incorrect generated file: %s", err)
   377  		}
   378  	}
   379  
   380  	if w.context != nil && file == fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH) {
   381  		src := fmt.Sprintf("package runtime; const theGoarch = `%s`", w.context.GOARCH)
   382  		f, err = parser.ParseFile(fset, filename, src, 0)
   383  		if err != nil {
   384  			log.Fatalf("incorrect generated file: %s", err)
   385  		}
   386  	}
   387  	if w.context != nil && file == fmt.Sprintf("zruntime_defs_%s_%s.go", w.context.GOOS, w.context.GOARCH) {
   388  		// Just enough to keep the api checker happy. Keep sorted.
   389  		src := "package runtime; type (" +
   390  			" _defer struct{};" +
   391  			" _func struct{};" +
   392  			" _panic struct{};" +
   393  			" _select struct{}; " +
   394  			" _type struct{};" +
   395  			" alg struct{};" +
   396  			" chantype struct{};" +
   397  			" context struct{};" + // windows
   398  			" eface struct{};" +
   399  			" epollevent struct{};" +
   400  			" funcval struct{};" +
   401  			" g struct{};" +
   402  			" gobuf struct{};" +
   403  			" hchan struct{};" +
   404  			" iface struct{};" +
   405  			" interfacetype struct{};" +
   406  			" itab struct{};" +
   407  			" keventt struct{};" +
   408  			" m struct{};" +
   409  			" maptype struct{};" +
   410  			" mcache struct{};" +
   411  			" mspan struct{};" +
   412  			" mutex struct{};" +
   413  			" note struct{};" +
   414  			" p struct{};" +
   415  			" parfor struct{};" +
   416  			" slicetype struct{};" +
   417  			" stkframe struct{};" +
   418  			" sudog struct{};" +
   419  			" timespec struct{};" +
   420  			" waitq struct{};" +
   421  			" wincallbackcontext struct{};" +
   422  			"); " +
   423  			"const (" +
   424  			" cb_max = 2000;" +
   425  			" _CacheLineSize = 64;" +
   426  			" _Gidle = 1;" +
   427  			" _Grunnable = 2;" +
   428  			" _Grunning = 3;" +
   429  			" _Gsyscall = 4;" +
   430  			" _Gwaiting = 5;" +
   431  			" _Gdead = 6;" +
   432  			" _Genqueue = 7;" +
   433  			" _Gcopystack = 8;" +
   434  			" _NSIG = 32;" +
   435  			" _FlagNoScan = iota;" +
   436  			" _FlagNoZero;" +
   437  			" _TinySize;" +
   438  			" _TinySizeClass;" +
   439  			" _MaxSmallSize;" +
   440  			" _PageShift;" +
   441  			" _PageSize;" +
   442  			" _PageMask;" +
   443  			" _BitsPerPointer;" +
   444  			" _BitsMask;" +
   445  			" _PointersPerByte;" +
   446  			" _MaxGCMask;" +
   447  			" _BitsDead;" +
   448  			" _BitsPointer;" +
   449  			" _MSpanInUse;" +
   450  			" _ConcurrentSweep;" +
   451  			" _KindBool;" +
   452  			" _KindInt;" +
   453  			" _KindInt8;" +
   454  			" _KindInt16;" +
   455  			" _KindInt32;" +
   456  			" _KindInt64;" +
   457  			" _KindUint;" +
   458  			" _KindUint8;" +
   459  			" _KindUint16;" +
   460  			" _KindUint32;" +
   461  			" _KindUint64;" +
   462  			" _KindUintptr;" +
   463  			" _KindFloat32;" +
   464  			" _KindFloat64;" +
   465  			" _KindComplex64;" +
   466  			" _KindComplex128;" +
   467  			" _KindArray;" +
   468  			" _KindChan;" +
   469  			" _KindFunc;" +
   470  			" _KindInterface;" +
   471  			" _KindMap;" +
   472  			" _KindPtr;" +
   473  			" _KindSlice;" +
   474  			" _KindString;" +
   475  			" _KindStruct;" +
   476  			" _KindUnsafePointer;" +
   477  			" _KindDirectIface;" +
   478  			" _KindGCProg;" +
   479  			" _KindNoPointers;" +
   480  			" _KindMask;" +
   481  			")"
   482  		f, err = parser.ParseFile(fset, filename, src, 0)
   483  		if err != nil {
   484  			log.Fatalf("incorrect generated file: %s", err)
   485  		}
   486  	}
   487  
   488  	if f == nil {
   489  		f, err = parser.ParseFile(fset, filename, nil, 0)
   490  		if err != nil {
   491  			return nil, err
   492  		}
   493  	}
   494  
   495  	parsedFileCache[filename] = f
   496  	return f, nil
   497  }
   498  
   499  func contains(list []string, s string) bool {
   500  	for _, t := range list {
   501  		if t == s {
   502  			return true
   503  		}
   504  	}
   505  	return false
   506  }
   507  
   508  // The package cache doesn't operate correctly in rare (so far artificial)
   509  // circumstances (issue 8425). Disable before debugging non-obvious errors
   510  // from the type-checker.
   511  const usePkgCache = true
   512  
   513  var (
   514  	pkgCache = map[string]*types.Package{} // map tagKey to package
   515  	pkgTags  = map[string][]string{}       // map import dir to list of relevant tags
   516  )
   517  
   518  // tagKey returns the tag-based key to use in the pkgCache.
   519  // It is a comma-separated string; the first part is dir, the rest tags.
   520  // The satisfied tags are derived from context but only those that
   521  // matter (the ones listed in the tags argument) are used.
   522  // The tags list, which came from go/build's Package.AllTags,
   523  // is known to be sorted.
   524  func tagKey(dir string, context *build.Context, tags []string) string {
   525  	ctags := map[string]bool{
   526  		context.GOOS:   true,
   527  		context.GOARCH: true,
   528  	}
   529  	if context.CgoEnabled {
   530  		ctags["cgo"] = true
   531  	}
   532  	for _, tag := range context.BuildTags {
   533  		ctags[tag] = true
   534  	}
   535  	// TODO: ReleaseTags (need to load default)
   536  	key := dir
   537  	for _, tag := range tags {
   538  		if ctags[tag] {
   539  			key += "," + tag
   540  		}
   541  	}
   542  	return key
   543  }
   544  
   545  // Importing is a sentinel taking the place in Walker.imported
   546  // for a package that is in the process of being imported.
   547  var importing types.Package
   548  
   549  func (w *Walker) Import(name string) (pkg *types.Package) {
   550  	pkg = w.imported[name]
   551  	if pkg != nil {
   552  		if pkg == &importing {
   553  			log.Fatalf("cycle importing package %q", name)
   554  		}
   555  		return pkg
   556  	}
   557  	w.imported[name] = &importing
   558  
   559  	// Determine package files.
   560  	dir := filepath.Join(w.root, filepath.FromSlash(name))
   561  	if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
   562  		log.Fatalf("no source in tree for package %q", pkg)
   563  	}
   564  
   565  	context := w.context
   566  	if context == nil {
   567  		context = &build.Default
   568  	}
   569  
   570  	// Look in cache.
   571  	// If we've already done an import with the same set
   572  	// of relevant tags, reuse the result.
   573  	var key string
   574  	if usePkgCache {
   575  		if tags, ok := pkgTags[dir]; ok {
   576  			key = tagKey(dir, context, tags)
   577  			if pkg := pkgCache[key]; pkg != nil {
   578  				w.imported[name] = pkg
   579  				return pkg
   580  			}
   581  		}
   582  	}
   583  
   584  	info, err := context.ImportDir(dir, 0)
   585  	if err != nil {
   586  		if _, nogo := err.(*build.NoGoError); nogo {
   587  			return
   588  		}
   589  		log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err)
   590  	}
   591  
   592  	// Save tags list first time we see a directory.
   593  	if usePkgCache {
   594  		if _, ok := pkgTags[dir]; !ok {
   595  			pkgTags[dir] = info.AllTags
   596  			key = tagKey(dir, context, info.AllTags)
   597  		}
   598  	}
   599  
   600  	filenames := append(append([]string{}, info.GoFiles...), info.CgoFiles...)
   601  
   602  	// Certain files only exist when building for the specified context.
   603  	// Add them manually.
   604  	if name == "runtime" {
   605  		n := fmt.Sprintf("zgoos_%s.go", w.context.GOOS)
   606  		if !contains(filenames, n) {
   607  			filenames = append(filenames, n)
   608  		}
   609  
   610  		n = fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH)
   611  		if !contains(filenames, n) {
   612  			filenames = append(filenames, n)
   613  		}
   614  
   615  		n = fmt.Sprintf("zruntime_defs_%s_%s.go", w.context.GOOS, w.context.GOARCH)
   616  		if !contains(filenames, n) {
   617  			filenames = append(filenames, n)
   618  		}
   619  	}
   620  
   621  	// Parse package files.
   622  	var files []*ast.File
   623  	for _, file := range filenames {
   624  		f, err := w.parseFile(dir, file)
   625  		if err != nil {
   626  			log.Fatalf("error parsing package %s: %s", name, err)
   627  		}
   628  		files = append(files, f)
   629  	}
   630  
   631  	// Type-check package files.
   632  	conf := types.Config{
   633  		IgnoreFuncBodies: true,
   634  		FakeImportC:      true,
   635  		Import: func(imports map[string]*types.Package, name string) (*types.Package, error) {
   636  			pkg := w.Import(name)
   637  			imports[name] = pkg
   638  			return pkg, nil
   639  		},
   640  	}
   641  	pkg, err = conf.Check(name, fset, files, nil)
   642  	if err != nil {
   643  		ctxt := "<no context>"
   644  		if w.context != nil {
   645  			ctxt = fmt.Sprintf("%s-%s", w.context.GOOS, w.context.GOARCH)
   646  		}
   647  		log.Fatalf("error typechecking package %s: %s (%s)", name, err, ctxt)
   648  	}
   649  
   650  	if usePkgCache {
   651  		pkgCache[key] = pkg
   652  	}
   653  
   654  	w.imported[name] = pkg
   655  	return
   656  }
   657  
   658  // pushScope enters a new scope (walking a package, type, node, etc)
   659  // and returns a function that will leave the scope (with sanity checking
   660  // for mismatched pushes & pops)
   661  func (w *Walker) pushScope(name string) (popFunc func()) {
   662  	w.scope = append(w.scope, name)
   663  	return func() {
   664  		if len(w.scope) == 0 {
   665  			log.Fatalf("attempt to leave scope %q with empty scope list", name)
   666  		}
   667  		if w.scope[len(w.scope)-1] != name {
   668  			log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope)
   669  		}
   670  		w.scope = w.scope[:len(w.scope)-1]
   671  	}
   672  }
   673  
   674  func sortedMethodNames(typ *types.Interface) []string {
   675  	n := typ.NumMethods()
   676  	list := make([]string, n)
   677  	for i := range list {
   678  		list[i] = typ.Method(i).Name()
   679  	}
   680  	sort.Strings(list)
   681  	return list
   682  }
   683  
   684  func (w *Walker) writeType(buf *bytes.Buffer, typ types.Type) {
   685  	switch typ := typ.(type) {
   686  	case *types.Basic:
   687  		s := typ.Name()
   688  		switch typ.Kind() {
   689  		case types.UnsafePointer:
   690  			s = "unsafe.Pointer"
   691  		case types.UntypedBool:
   692  			s = "ideal-bool"
   693  		case types.UntypedInt:
   694  			s = "ideal-int"
   695  		case types.UntypedRune:
   696  			// "ideal-char" for compatibility with old tool
   697  			// TODO(gri) change to "ideal-rune"
   698  			s = "ideal-char"
   699  		case types.UntypedFloat:
   700  			s = "ideal-float"
   701  		case types.UntypedComplex:
   702  			s = "ideal-complex"
   703  		case types.UntypedString:
   704  			s = "ideal-string"
   705  		case types.UntypedNil:
   706  			panic("should never see untyped nil type")
   707  		default:
   708  			switch s {
   709  			case "byte":
   710  				s = "uint8"
   711  			case "rune":
   712  				s = "int32"
   713  			}
   714  		}
   715  		buf.WriteString(s)
   716  
   717  	case *types.Array:
   718  		fmt.Fprintf(buf, "[%d]", typ.Len())
   719  		w.writeType(buf, typ.Elem())
   720  
   721  	case *types.Slice:
   722  		buf.WriteString("[]")
   723  		w.writeType(buf, typ.Elem())
   724  
   725  	case *types.Struct:
   726  		buf.WriteString("struct")
   727  
   728  	case *types.Pointer:
   729  		buf.WriteByte('*')
   730  		w.writeType(buf, typ.Elem())
   731  
   732  	case *types.Tuple:
   733  		panic("should never see a tuple type")
   734  
   735  	case *types.Signature:
   736  		buf.WriteString("func")
   737  		w.writeSignature(buf, typ)
   738  
   739  	case *types.Interface:
   740  		buf.WriteString("interface{")
   741  		if typ.NumMethods() > 0 {
   742  			buf.WriteByte(' ')
   743  			buf.WriteString(strings.Join(sortedMethodNames(typ), ", "))
   744  			buf.WriteByte(' ')
   745  		}
   746  		buf.WriteString("}")
   747  
   748  	case *types.Map:
   749  		buf.WriteString("map[")
   750  		w.writeType(buf, typ.Key())
   751  		buf.WriteByte(']')
   752  		w.writeType(buf, typ.Elem())
   753  
   754  	case *types.Chan:
   755  		var s string
   756  		switch typ.Dir() {
   757  		case ast.SEND:
   758  			s = "chan<- "
   759  		case ast.RECV:
   760  			s = "<-chan "
   761  		default:
   762  			s = "chan "
   763  		}
   764  		buf.WriteString(s)
   765  		w.writeType(buf, typ.Elem())
   766  
   767  	case *types.Named:
   768  		obj := typ.Obj()
   769  		pkg := obj.Pkg()
   770  		if pkg != nil && pkg != w.current {
   771  			buf.WriteString(pkg.Name())
   772  			buf.WriteByte('.')
   773  		}
   774  		buf.WriteString(typ.Obj().Name())
   775  
   776  	default:
   777  		panic(fmt.Sprintf("unknown type %T", typ))
   778  	}
   779  }
   780  
   781  func (w *Walker) writeSignature(buf *bytes.Buffer, sig *types.Signature) {
   782  	w.writeParams(buf, sig.Params(), sig.IsVariadic())
   783  	switch res := sig.Results(); res.Len() {
   784  	case 0:
   785  		// nothing to do
   786  	case 1:
   787  		buf.WriteByte(' ')
   788  		w.writeType(buf, res.At(0).Type())
   789  	default:
   790  		buf.WriteByte(' ')
   791  		w.writeParams(buf, res, false)
   792  	}
   793  }
   794  
   795  func (w *Walker) writeParams(buf *bytes.Buffer, t *types.Tuple, variadic bool) {
   796  	buf.WriteByte('(')
   797  	for i, n := 0, t.Len(); i < n; i++ {
   798  		if i > 0 {
   799  			buf.WriteString(", ")
   800  		}
   801  		typ := t.At(i).Type()
   802  		if variadic && i+1 == n {
   803  			buf.WriteString("...")
   804  			typ = typ.(*types.Slice).Elem()
   805  		}
   806  		w.writeType(buf, typ)
   807  	}
   808  	buf.WriteByte(')')
   809  }
   810  
   811  func (w *Walker) typeString(typ types.Type) string {
   812  	var buf bytes.Buffer
   813  	w.writeType(&buf, typ)
   814  	return buf.String()
   815  }
   816  
   817  func (w *Walker) signatureString(sig *types.Signature) string {
   818  	var buf bytes.Buffer
   819  	w.writeSignature(&buf, sig)
   820  	return buf.String()
   821  }
   822  
   823  func (w *Walker) emitObj(obj types.Object) {
   824  	switch obj := obj.(type) {
   825  	case *types.Const:
   826  		w.emitf("const %s %s", obj.Name(), w.typeString(obj.Type()))
   827  		w.emitf("const %s = %s", obj.Name(), obj.Val())
   828  	case *types.Var:
   829  		w.emitf("var %s %s", obj.Name(), w.typeString(obj.Type()))
   830  	case *types.TypeName:
   831  		w.emitType(obj)
   832  	case *types.Func:
   833  		w.emitFunc(obj)
   834  	default:
   835  		panic("unknown object: " + obj.String())
   836  	}
   837  }
   838  
   839  func (w *Walker) emitType(obj *types.TypeName) {
   840  	name := obj.Name()
   841  	typ := obj.Type()
   842  	switch typ := typ.Underlying().(type) {
   843  	case *types.Struct:
   844  		w.emitStructType(name, typ)
   845  	case *types.Interface:
   846  		w.emitIfaceType(name, typ)
   847  		return // methods are handled by emitIfaceType
   848  	default:
   849  		w.emitf("type %s %s", name, w.typeString(typ.Underlying()))
   850  	}
   851  
   852  	// emit methods with value receiver
   853  	var methodNames map[string]bool
   854  	vset := typ.MethodSet()
   855  	for i, n := 0, vset.Len(); i < n; i++ {
   856  		m := vset.At(i)
   857  		if m.Obj().IsExported() {
   858  			w.emitMethod(m)
   859  			if methodNames == nil {
   860  				methodNames = make(map[string]bool)
   861  			}
   862  			methodNames[m.Obj().Name()] = true
   863  		}
   864  	}
   865  
   866  	// emit methods with pointer receiver; exclude
   867  	// methods that we have emitted already
   868  	// (the method set of *T includes the methods of T)
   869  	pset := types.NewPointer(typ).MethodSet()
   870  	for i, n := 0, pset.Len(); i < n; i++ {
   871  		m := pset.At(i)
   872  		if m.Obj().IsExported() && !methodNames[m.Obj().Name()] {
   873  			w.emitMethod(m)
   874  		}
   875  	}
   876  }
   877  
   878  func (w *Walker) emitStructType(name string, typ *types.Struct) {
   879  	typeStruct := fmt.Sprintf("type %s struct", name)
   880  	w.emitf(typeStruct)
   881  	defer w.pushScope(typeStruct)()
   882  
   883  	for i := 0; i < typ.NumFields(); i++ {
   884  		f := typ.Field(i)
   885  		if !f.IsExported() {
   886  			continue
   887  		}
   888  		typ := f.Type()
   889  		if f.Anonymous() {
   890  			w.emitf("embedded %s", w.typeString(typ))
   891  			continue
   892  		}
   893  		w.emitf("%s %s", f.Name(), w.typeString(typ))
   894  	}
   895  }
   896  
   897  func (w *Walker) emitIfaceType(name string, typ *types.Interface) {
   898  	pop := w.pushScope("type " + name + " interface")
   899  
   900  	var methodNames []string
   901  	complete := true
   902  	mset := typ.MethodSet()
   903  	for i, n := 0, mset.Len(); i < n; i++ {
   904  		m := mset.At(i).Obj().(*types.Func)
   905  		if !m.IsExported() {
   906  			complete = false
   907  			continue
   908  		}
   909  		methodNames = append(methodNames, m.Name())
   910  		w.emitf("%s%s", m.Name(), w.signatureString(m.Type().(*types.Signature)))
   911  	}
   912  
   913  	if !complete {
   914  		// The method set has unexported methods, so all the
   915  		// implementations are provided by the same package,
   916  		// so the method set can be extended. Instead of recording
   917  		// the full set of names (below), record only that there were
   918  		// unexported methods. (If the interface shrinks, we will notice
   919  		// because a method signature emitted during the last loop
   920  		// will disappear.)
   921  		w.emitf("unexported methods")
   922  	}
   923  
   924  	pop()
   925  
   926  	if !complete {
   927  		return
   928  	}
   929  
   930  	if len(methodNames) == 0 {
   931  		w.emitf("type %s interface {}", name)
   932  		return
   933  	}
   934  
   935  	sort.Strings(methodNames)
   936  	w.emitf("type %s interface { %s }", name, strings.Join(methodNames, ", "))
   937  }
   938  
   939  func (w *Walker) emitFunc(f *types.Func) {
   940  	sig := f.Type().(*types.Signature)
   941  	if sig.Recv() != nil {
   942  		panic("method considered a regular function: " + f.String())
   943  	}
   944  	w.emitf("func %s%s", f.Name(), w.signatureString(sig))
   945  }
   946  
   947  func (w *Walker) emitMethod(m *types.Selection) {
   948  	sig := m.Type().(*types.Signature)
   949  	recv := sig.Recv().Type()
   950  	// report exported methods with unexported receiver base type
   951  	if true {
   952  		base := recv
   953  		if p, _ := recv.(*types.Pointer); p != nil {
   954  			base = p.Elem()
   955  		}
   956  		if obj := base.(*types.Named).Obj(); !obj.IsExported() {
   957  			log.Fatalf("exported method with unexported receiver base type: %s", m)
   958  		}
   959  	}
   960  	w.emitf("method (%s) %s%s", w.typeString(recv), m.Obj().Name(), w.signatureString(sig))
   961  }
   962  
   963  func (w *Walker) emitf(format string, args ...interface{}) {
   964  	f := strings.Join(w.scope, ", ") + ", " + fmt.Sprintf(format, args...)
   965  	if strings.Contains(f, "\n") {
   966  		panic("feature contains newlines: " + f)
   967  	}
   968  
   969  	if _, dup := w.features[f]; dup {
   970  		panic("duplicate feature inserted: " + f)
   971  	}
   972  	w.features[f] = true
   973  
   974  	if *verbose {
   975  		log.Printf("feature: %s", f)
   976  	}
   977  }