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