2012-01-25 17:47:57 -08:00
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
2013-08-08 14:10:59 -07:00
// Binary api computes the exported API of a set of Go packages.
2012-01-25 17:47:57 -08:00
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
2015-04-07 11:04:01 -07:00
"go/types"
2012-10-30 11:23:44 +01:00
"io"
2012-01-25 17:47:57 -08:00
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
2012-10-30 13:12:59 +01:00
"regexp"
2012-06-08 13:44:13 -04:00
"runtime"
2012-01-25 17:47:57 -08:00
"sort"
"strings"
)
// Flags
var (
2013-05-06 17:25:09 -07:00
checkFile = flag . String ( "c" , "" , "optional comma-separated filename(s) to check API against" )
2012-10-04 11:35:17 +10:00
allowNew = flag . Bool ( "allow_new" , true , "allow API additions" )
exceptFile = flag . String ( "except" , "" , "optional filename of packages that are allowed to change without triggering a failure in the tool" )
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." )
verbose = flag . Bool ( "v" , false , "verbose debugging" )
forceCtx = flag . String ( "contexts" , "" , "optional comma-separated list of <goos>-<goarch>[-cgo] to override default contexts." )
2012-01-25 17:47:57 -08:00
)
2012-05-23 13:45:53 -07:00
// contexts are the default contexts which are scanned, unless
// overridden by the -contexts flag.
2012-02-07 18:13:11 -08:00
var contexts = [ ] * build . Context {
{ GOOS : "linux" , GOARCH : "386" , CgoEnabled : true } ,
{ GOOS : "linux" , GOARCH : "386" } ,
{ GOOS : "linux" , GOARCH : "amd64" , CgoEnabled : true } ,
{ GOOS : "linux" , GOARCH : "amd64" } ,
2013-05-21 21:47:32 +08:00
{ GOOS : "linux" , GOARCH : "arm" , CgoEnabled : true } ,
2012-06-04 15:21:58 +08:00
{ GOOS : "linux" , GOARCH : "arm" } ,
2012-02-07 18:13:11 -08:00
{ GOOS : "darwin" , GOARCH : "386" , CgoEnabled : true } ,
{ GOOS : "darwin" , GOARCH : "386" } ,
{ GOOS : "darwin" , GOARCH : "amd64" , CgoEnabled : true } ,
{ GOOS : "darwin" , GOARCH : "amd64" } ,
{ GOOS : "windows" , GOARCH : "amd64" } ,
{ GOOS : "windows" , GOARCH : "386" } ,
2013-05-21 21:47:32 +08:00
{ GOOS : "freebsd" , GOARCH : "386" , CgoEnabled : true } ,
2012-06-01 18:42:36 -07:00
{ GOOS : "freebsd" , GOARCH : "386" } ,
2013-05-21 21:47:32 +08:00
{ GOOS : "freebsd" , GOARCH : "amd64" , CgoEnabled : true } ,
{ GOOS : "freebsd" , GOARCH : "amd64" } ,
{ GOOS : "freebsd" , GOARCH : "arm" , CgoEnabled : true } ,
{ GOOS : "freebsd" , GOARCH : "arm" } ,
{ GOOS : "netbsd" , GOARCH : "386" , CgoEnabled : true } ,
{ GOOS : "netbsd" , GOARCH : "386" } ,
{ GOOS : "netbsd" , GOARCH : "amd64" , CgoEnabled : true } ,
{ GOOS : "netbsd" , GOARCH : "amd64" } ,
{ GOOS : "netbsd" , GOARCH : "arm" , CgoEnabled : true } ,
{ GOOS : "netbsd" , GOARCH : "arm" } ,
{ GOOS : "openbsd" , GOARCH : "386" , CgoEnabled : true } ,
{ GOOS : "openbsd" , GOARCH : "386" } ,
{ GOOS : "openbsd" , GOARCH : "amd64" , CgoEnabled : true } ,
{ GOOS : "openbsd" , GOARCH : "amd64" } ,
2012-02-07 18:13:11 -08:00
}
func contextName ( c * build . Context ) string {
s := c . GOOS + "-" + c . GOARCH
if c . CgoEnabled {
return s + "-cgo"
}
return s
}
2012-05-23 13:45:53 -07:00
func parseContext ( c string ) * build . Context {
parts := strings . Split ( c , "-" )
if len ( parts ) < 2 {
log . Fatalf ( "bad context: %q" , c )
}
bc := & build . Context {
GOOS : parts [ 0 ] ,
GOARCH : parts [ 1 ] ,
}
if len ( parts ) == 3 {
if parts [ 2 ] == "cgo" {
bc . CgoEnabled = true
} else {
log . Fatalf ( "bad context: %q" , c )
}
}
return bc
}
func setContexts ( ) {
contexts = [ ] * build . Context { }
for _ , c := range strings . Split ( * forceCtx , "," ) {
contexts = append ( contexts , parseContext ( c ) )
}
}
2014-12-15 11:08:37 +11:00
var internalPkg = regexp . MustCompile ( ` (^|/)internal($|/) ` )
2014-07-21 12:06:30 -07:00
2012-01-25 17:47:57 -08:00
func main ( ) {
flag . Parse ( )
2014-12-15 11:08:37 +11:00
if ! strings . Contains ( runtime . Version ( ) , "weekly" ) && ! strings . Contains ( runtime . Version ( ) , "devel" ) {
2012-06-08 13:44:13 -04:00
if * nextFile != "" {
fmt . Printf ( "Go version is %q, ignoring -next %s\n" , runtime . Version ( ) , * nextFile )
* nextFile = ""
}
}
2012-05-23 13:45:53 -07:00
if * forceCtx != "" {
setContexts ( )
}
for _ , c := range contexts {
c . Compiler = build . Default . Compiler
}
2013-08-08 14:10:59 -07:00
var pkgNames [ ] string
2012-01-25 17:47:57 -08:00
if flag . NArg ( ) > 0 {
2013-08-08 14:10:59 -07:00
pkgNames = flag . Args ( )
2012-01-25 17:47:57 -08:00
} else {
stds , err := exec . Command ( "go" , "list" , "std" ) . Output ( )
if err != nil {
log . Fatal ( err )
}
2014-07-21 12:06:30 -07:00
for _ , pkg := range strings . Fields ( string ( stds ) ) {
if ! internalPkg . MatchString ( pkg ) {
pkgNames = append ( pkgNames , pkg )
}
}
2012-01-25 17:47:57 -08:00
}
2012-02-07 18:13:11 -08:00
var featureCtx = make ( map [ string ] map [ string ] bool ) // feature -> context name -> true
for _ , context := range contexts {
2014-09-08 00:06:45 -04:00
w := NewWalker ( context , filepath . Join ( build . Default . GOROOT , "src" ) )
2012-02-07 18:13:11 -08:00
2013-08-08 14:10:59 -07:00
for _ , name := range pkgNames {
// - Package "unsafe" contains special signatures requiring
// extra care when printing them - ignore since it is not
// going to change w/o a language change.
// - We don't care about the API of commands.
if name != "unsafe" && ! strings . HasPrefix ( name , "cmd/" ) {
2013-09-11 14:42:34 -04:00
if name == "runtime/cgo" && ! context . CgoEnabled {
// w.Import(name) will return nil
continue
}
2015-04-10 17:50:06 -07:00
pkg , _ := w . Import ( name )
w . export ( pkg )
2012-02-07 18:13:11 -08:00
}
}
2013-08-08 14:10:59 -07:00
2012-02-07 18:13:11 -08:00
ctxName := contextName ( context )
for _ , f := range w . Features ( ) {
if featureCtx [ f ] == nil {
featureCtx [ f ] = make ( map [ string ] bool )
}
featureCtx [ f ] [ ctxName ] = true
}
2012-01-29 21:04:13 -08:00
}
2012-01-25 17:47:57 -08:00
2012-02-07 18:13:11 -08:00
var features [ ] string
for f , cmap := range featureCtx {
if len ( cmap ) == len ( contexts ) {
features = append ( features , f )
2012-01-25 17:47:57 -08:00
continue
}
2012-02-07 18:13:11 -08:00
comma := strings . Index ( f , "," )
for cname := range cmap {
f2 := fmt . Sprintf ( "%s (%s)%s" , f [ : comma ] , cname , f [ comma : ] )
features = append ( features , f2 )
2012-01-25 17:47:57 -08:00
}
}
2012-05-22 18:41:20 -07:00
fail := false
defer func ( ) {
if fail {
os . Exit ( 1 )
}
} ( )
2012-01-25 17:47:57 -08:00
bw := bufio . NewWriter ( os . Stdout )
defer bw . Flush ( )
2012-05-22 18:41:20 -07:00
if * checkFile == "" {
2013-01-19 22:20:46 -08:00
sort . Strings ( features )
2012-05-22 18:41:20 -07:00
for _ , f := range features {
2013-08-08 14:10:59 -07:00
fmt . Fprintln ( bw , f )
2012-01-25 17:47:57 -08:00
}
2012-05-22 18:41:20 -07:00
return
}
2013-05-06 17:25:09 -07:00
var required [ ] string
for _ , file := range strings . Split ( * checkFile , "," ) {
required = append ( required , fileFeatures ( file ) ... )
}
2012-10-30 11:23:44 +01:00
optional := fileFeatures ( * nextFile )
exception := fileFeatures ( * exceptFile )
2015-04-08 04:27:58 -04:00
fail = ! compareAPI ( bw , features , required , optional , exception ,
* allowNew && strings . Contains ( runtime . Version ( ) , "devel" ) )
2012-10-30 11:23:44 +01:00
}
2012-05-22 18:41:20 -07:00
2013-08-08 14:10:59 -07:00
// export emits the exported package features.
func ( w * Walker ) export ( pkg * types . Package ) {
if * verbose {
log . Println ( pkg )
}
pop := w . pushScope ( "pkg " + pkg . Path ( ) )
w . current = pkg
scope := pkg . Scope ( )
for _ , name := range scope . Names ( ) {
if ast . IsExported ( name ) {
w . emitObj ( scope . Lookup ( name ) )
}
}
pop ( )
}
2012-10-30 13:12:59 +01:00
func set ( items [ ] string ) map [ string ] bool {
s := make ( map [ string ] bool )
for _ , v := range items {
s [ v ] = true
}
return s
}
var spaceParensRx = regexp . MustCompile ( ` \(\S+?\) ` )
func featureWithoutContext ( f string ) string {
if ! strings . Contains ( f , "(" ) {
return f
}
return spaceParensRx . ReplaceAllString ( f , "" )
}
2015-04-08 04:27:58 -04:00
func compareAPI ( w io . Writer , features , required , optional , exception [ ] string , allowAdd bool ) ( ok bool ) {
2012-10-30 11:23:44 +01:00
ok = true
2012-05-22 18:41:20 -07:00
2012-10-30 13:12:59 +01:00
optionalSet := set ( optional )
exceptionSet := set ( exception )
featureSet := set ( features )
2012-10-30 11:23:44 +01:00
sort . Strings ( features )
sort . Strings ( required )
2012-10-04 11:35:17 +10:00
2012-05-22 18:41:20 -07:00
take := func ( sl * [ ] string ) string {
s := ( * sl ) [ 0 ]
* sl = ( * sl ) [ 1 : ]
return s
}
for len ( required ) > 0 || len ( features ) > 0 {
switch {
2012-10-30 13:12:59 +01:00
case len ( features ) == 0 || ( len ( required ) > 0 && required [ 0 ] < features [ 0 ] ) :
2012-10-04 11:35:17 +10:00
feature := take ( & required )
2012-10-30 11:23:44 +01:00
if exceptionSet [ feature ] {
2013-05-14 09:43:56 -07:00
// An "unfortunate" case: the feature was once
// included in the API (e.g. go1.txt), but was
// subsequently removed. These are already
// acknowledged by being in the file
// "api/except.txt". No need to print them out
// here.
2012-10-30 13:12:59 +01:00
} else if featureSet [ featureWithoutContext ( feature ) ] {
// okay.
2012-10-04 11:35:17 +10:00
} else {
2012-10-30 11:23:44 +01:00
fmt . Fprintf ( w , "-%s\n" , feature )
ok = false // broke compatibility
2012-10-04 11:35:17 +10:00
}
2012-10-30 13:12:59 +01:00
case len ( required ) == 0 || ( len ( features ) > 0 && required [ 0 ] > features [ 0 ] ) :
2012-05-22 18:41:20 -07:00
newFeature := take ( & features )
2012-10-30 11:23:44 +01:00
if optionalSet [ newFeature ] {
2012-05-22 18:41:20 -07:00
// Known added feature to the upcoming release.
// Delete it from the map so we can detect any upcoming features
// which were never seen. (so we can clean up the nextFile)
2012-10-30 11:23:44 +01:00
delete ( optionalSet , newFeature )
2012-05-22 18:41:20 -07:00
} else {
2012-10-30 11:23:44 +01:00
fmt . Fprintf ( w , "+%s\n" , newFeature )
2015-04-08 04:27:58 -04:00
if ! allowAdd {
2012-10-30 11:23:44 +01:00
ok = false // we're in lock-down mode for next release
2012-05-22 18:41:20 -07:00
}
2012-01-25 17:47:57 -08:00
}
2012-05-22 18:41:20 -07:00
default :
take ( & required )
take ( & features )
2012-01-25 17:47:57 -08:00
}
}
2012-05-22 18:41:20 -07:00
2012-09-27 15:39:56 +10:00
// In next file, but not in API.
2012-05-22 18:41:20 -07:00
var missing [ ] string
2012-10-30 11:23:44 +01:00
for feature := range optionalSet {
2012-05-22 18:41:20 -07:00
missing = append ( missing , feature )
}
sort . Strings ( missing )
for _ , feature := range missing {
2012-10-30 11:23:44 +01:00
fmt . Fprintf ( w , "±%s\n" , feature )
2012-05-22 18:41:20 -07:00
}
2012-10-30 11:23:44 +01:00
return
2012-05-22 18:41:20 -07:00
}
func fileFeatures ( filename string ) [ ] string {
2012-10-30 11:23:44 +01:00
if filename == "" {
return nil
}
2012-05-22 18:41:20 -07:00
bs , err := ioutil . ReadFile ( filename )
if err != nil {
log . Fatalf ( "Error reading file %s: %v" , filename , err )
}
2014-12-05 14:04:17 -05:00
lines := strings . Split ( string ( bs ) , "\n" )
var nonblank [ ] string
for _ , line := range lines {
line = strings . TrimSpace ( line )
if line != "" && ! strings . HasPrefix ( line , "#" ) {
nonblank = append ( nonblank , line )
}
2012-06-08 13:44:13 -04:00
}
2014-12-05 14:04:17 -05:00
return nonblank
2012-01-25 17:47:57 -08:00
}
2012-11-13 09:59:46 -08:00
var fset = token . NewFileSet ( )
2012-01-25 17:47:57 -08:00
type Walker struct {
2013-08-08 14:10:59 -07:00
context * build . Context
root string
scope [ ] string
current * types . Package
features map [ string ] bool // set
imported map [ string ] * types . Package // packages already imported
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func NewWalker ( context * build . Context , root string ) * Walker {
2012-01-25 17:47:57 -08:00
return & Walker {
2013-08-08 14:10:59 -07:00
context : context ,
root : root ,
features : map [ string ] bool { } ,
imported : map [ string ] * types . Package { "unsafe" : types . Unsafe } ,
2012-01-25 17:47:57 -08:00
}
}
func ( w * Walker ) Features ( ) ( fs [ ] string ) {
for f := range w . features {
fs = append ( fs , f )
}
sort . Strings ( fs )
return
}
2013-08-08 14:10:59 -07:00
var parsedFileCache = make ( map [ string ] * ast . File )
func ( w * Walker ) parseFile ( dir , file string ) ( * ast . File , error ) {
filename := filepath . Join ( dir , file )
2014-12-05 16:24:20 -05:00
if f := parsedFileCache [ filename ] ; f != nil {
2013-08-08 14:10:59 -07:00
return f , nil
}
2014-12-05 16:24:20 -05:00
f , err := parser . ParseFile ( fset , filename , nil , 0 )
if err != nil {
return nil , err
2012-11-19 13:50:20 -08:00
}
2013-08-08 14:10:59 -07:00
parsedFileCache [ filename ] = f
2014-12-05 16:24:20 -05:00
2013-08-08 14:10:59 -07:00
return f , nil
2012-11-19 13:50:20 -08:00
}
2013-08-08 14:10:59 -07:00
func contains ( list [ ] string , s string ) bool {
for _ , t := range list {
if t == s {
return true
}
2012-01-29 21:04:13 -08:00
}
2013-08-08 14:10:59 -07:00
return false
}
2014-09-15 16:40:43 -07:00
// The package cache doesn't operate correctly in rare (so far artificial)
// circumstances (issue 8425). Disable before debugging non-obvious errors
// from the type-checker.
const usePkgCache = true
2013-08-09 18:44:00 -04:00
var (
pkgCache = map [ string ] * types . Package { } // map tagKey to package
pkgTags = map [ string ] [ ] string { } // map import dir to list of relevant tags
)
// tagKey returns the tag-based key to use in the pkgCache.
// It is a comma-separated string; the first part is dir, the rest tags.
// The satisfied tags are derived from context but only those that
// matter (the ones listed in the tags argument) are used.
// The tags list, which came from go/build's Package.AllTags,
// is known to be sorted.
func tagKey ( dir string , context * build . Context , tags [ ] string ) string {
ctags := map [ string ] bool {
context . GOOS : true ,
context . GOARCH : true ,
}
if context . CgoEnabled {
ctags [ "cgo" ] = true
}
for _ , tag := range context . BuildTags {
ctags [ tag ] = true
}
// TODO: ReleaseTags (need to load default)
key := dir
for _ , tag := range tags {
if ctags [ tag ] {
key += "," + tag
}
}
return key
}
2013-08-08 14:10:59 -07:00
// Importing is a sentinel taking the place in Walker.imported
// for a package that is in the process of being imported.
var importing types . Package
2015-04-10 17:50:06 -07:00
func ( w * Walker ) Import ( name string ) ( * types . Package , error ) {
pkg := w . imported [ name ]
2013-08-08 14:10:59 -07:00
if pkg != nil {
if pkg == & importing {
log . Fatalf ( "cycle importing package %q" , name )
}
2015-04-10 17:50:06 -07:00
return pkg , nil
2013-08-08 14:10:59 -07:00
}
w . imported [ name ] = & importing
2015-10-14 20:41:36 +00:00
root := w . root
if strings . HasPrefix ( name , "golang.org/x/" ) {
root = filepath . Join ( root , "vendor" )
}
2013-08-08 14:10:59 -07:00
// Determine package files.
2015-10-14 20:41:36 +00:00
dir := filepath . Join ( root , filepath . FromSlash ( name ) )
2013-08-08 14:10:59 -07:00
if fi , err := os . Stat ( dir ) ; err != nil || ! fi . IsDir ( ) {
2015-10-14 20:41:36 +00:00
log . Fatalf ( "no source in tree for import %q: %v" , name , err )
2013-08-08 14:10:59 -07:00
}
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
context := w . context
if context == nil {
context = & build . Default
2012-02-07 18:13:11 -08:00
}
2013-08-09 18:44:00 -04:00
// Look in cache.
// If we've already done an import with the same set
// of relevant tags, reuse the result.
var key string
2014-09-15 16:40:43 -07:00
if usePkgCache {
if tags , ok := pkgTags [ dir ] ; ok {
key = tagKey ( dir , context , tags )
if pkg := pkgCache [ key ] ; pkg != nil {
w . imported [ name ] = pkg
2015-04-10 17:50:06 -07:00
return pkg , nil
2014-09-15 16:40:43 -07:00
}
2013-08-09 18:44:00 -04:00
}
}
2013-08-08 14:10:59 -07:00
info , err := context . ImportDir ( dir , 0 )
2012-01-25 17:47:57 -08:00
if err != nil {
2013-08-08 14:10:59 -07:00
if _ , nogo := err . ( * build . NoGoError ) ; nogo {
2015-04-10 17:50:06 -07:00
return nil , nil
2012-02-07 18:13:11 -08:00
}
2012-01-25 17:47:57 -08:00
log . Fatalf ( "pkg %q, dir %q: ScanDir: %v" , name , dir , err )
}
2013-08-09 18:44:00 -04:00
// Save tags list first time we see a directory.
2014-09-15 16:40:43 -07:00
if usePkgCache {
if _ , ok := pkgTags [ dir ] ; ! ok {
pkgTags [ dir ] = info . AllTags
key = tagKey ( dir , context , info . AllTags )
}
2013-08-09 18:44:00 -04:00
}
2013-08-08 14:10:59 -07:00
filenames := append ( append ( [ ] string { } , info . GoFiles ... ) , info . CgoFiles ... )
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
// Parse package files.
var files [ ] * ast . File
for _ , file := range filenames {
f , err := w . parseFile ( dir , file )
if err != nil {
log . Fatalf ( "error parsing package %s: %s" , name , err )
2012-02-21 07:37:25 +01:00
}
2013-08-08 14:10:59 -07:00
files = append ( files , f )
2012-02-21 07:37:25 +01:00
}
2013-08-08 14:10:59 -07:00
// Type-check package files.
conf := types . Config {
IgnoreFuncBodies : true ,
FakeImportC : true ,
2015-04-10 17:50:06 -07:00
Importer : w ,
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
pkg , err = conf . Check ( name , fset , files , nil )
if err != nil {
ctxt := "<no context>"
if w . context != nil {
ctxt = fmt . Sprintf ( "%s-%s" , w . context . GOOS , w . context . GOARCH )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
log . Fatalf ( "error typechecking package %s: %s (%s)" , name , err , ctxt )
2012-01-25 17:47:57 -08:00
}
2014-09-15 16:40:43 -07:00
if usePkgCache {
pkgCache [ key ] = pkg
}
2013-08-09 18:44:00 -04:00
2013-08-08 14:10:59 -07:00
w . imported [ name ] = pkg
2015-04-10 17:50:06 -07:00
return pkg , nil
2012-01-25 17:47:57 -08:00
}
// pushScope enters a new scope (walking a package, type, node, etc)
// and returns a function that will leave the scope (with sanity checking
// for mismatched pushes & pops)
func ( w * Walker ) pushScope ( name string ) ( popFunc func ( ) ) {
w . scope = append ( w . scope , name )
return func ( ) {
if len ( w . scope ) == 0 {
log . Fatalf ( "attempt to leave scope %q with empty scope list" , name )
}
if w . scope [ len ( w . scope ) - 1 ] != name {
log . Fatalf ( "attempt to leave scope %q, but scope is currently %#v" , name , w . scope )
}
w . scope = w . scope [ : len ( w . scope ) - 1 ]
}
}
2013-08-08 14:10:59 -07:00
func sortedMethodNames ( typ * types . Interface ) [ ] string {
n := typ . NumMethods ( )
list := make ( [ ] string , n )
for i := range list {
list [ i ] = typ . Method ( i ) . Name ( )
}
sort . Strings ( list )
return list
}
func ( w * Walker ) writeType ( buf * bytes . Buffer , typ types . Type ) {
switch typ := typ . ( type ) {
case * types . Basic :
s := typ . Name ( )
switch typ . Kind ( ) {
case types . UnsafePointer :
s = "unsafe.Pointer"
case types . UntypedBool :
s = "ideal-bool"
case types . UntypedInt :
s = "ideal-int"
case types . UntypedRune :
// "ideal-char" for compatibility with old tool
// TODO(gri) change to "ideal-rune"
s = "ideal-char"
case types . UntypedFloat :
s = "ideal-float"
case types . UntypedComplex :
s = "ideal-complex"
case types . UntypedString :
s = "ideal-string"
case types . UntypedNil :
panic ( "should never see untyped nil type" )
default :
switch s {
case "byte" :
s = "uint8"
case "rune" :
s = "int32"
}
}
buf . WriteString ( s )
case * types . Array :
fmt . Fprintf ( buf , "[%d]" , typ . Len ( ) )
w . writeType ( buf , typ . Elem ( ) )
case * types . Slice :
buf . WriteString ( "[]" )
w . writeType ( buf , typ . Elem ( ) )
case * types . Struct :
buf . WriteString ( "struct" )
case * types . Pointer :
buf . WriteByte ( '*' )
w . writeType ( buf , typ . Elem ( ) )
case * types . Tuple :
panic ( "should never see a tuple type" )
case * types . Signature :
buf . WriteString ( "func" )
w . writeSignature ( buf , typ )
case * types . Interface :
buf . WriteString ( "interface{" )
if typ . NumMethods ( ) > 0 {
buf . WriteByte ( ' ' )
buf . WriteString ( strings . Join ( sortedMethodNames ( typ ) , ", " ) )
buf . WriteByte ( ' ' )
}
buf . WriteString ( "}" )
case * types . Map :
buf . WriteString ( "map[" )
w . writeType ( buf , typ . Key ( ) )
buf . WriteByte ( ']' )
w . writeType ( buf , typ . Elem ( ) )
case * types . Chan :
var s string
switch typ . Dir ( ) {
2015-04-07 11:04:01 -07:00
case types . SendOnly :
2013-08-08 14:10:59 -07:00
s = "chan<- "
2015-04-07 11:04:01 -07:00
case types . RecvOnly :
2013-08-08 14:10:59 -07:00
s = "<-chan "
2015-04-07 11:04:01 -07:00
case types . SendRecv :
2013-08-08 14:10:59 -07:00
s = "chan "
2015-04-07 11:04:01 -07:00
default :
panic ( "unreachable" )
2012-01-29 21:04:13 -08:00
}
2013-08-08 14:10:59 -07:00
buf . WriteString ( s )
w . writeType ( buf , typ . Elem ( ) )
2012-01-29 21:04:13 -08:00
2013-08-08 14:10:59 -07:00
case * types . Named :
obj := typ . Obj ( )
pkg := obj . Pkg ( )
if pkg != nil && pkg != w . current {
buf . WriteString ( pkg . Name ( ) )
buf . WriteByte ( '.' )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
buf . WriteString ( typ . Obj ( ) . Name ( ) )
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
default :
panic ( fmt . Sprintf ( "unknown type %T" , typ ) )
}
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) writeSignature ( buf * bytes . Buffer , sig * types . Signature ) {
2015-04-07 11:04:01 -07:00
w . writeParams ( buf , sig . Params ( ) , sig . Variadic ( ) )
2013-08-08 14:10:59 -07:00
switch res := sig . Results ( ) ; res . Len ( ) {
case 0 :
// nothing to do
case 1 :
buf . WriteByte ( ' ' )
w . writeType ( buf , res . At ( 0 ) . Type ( ) )
default :
buf . WriteByte ( ' ' )
w . writeParams ( buf , res , false )
}
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) writeParams ( buf * bytes . Buffer , t * types . Tuple , variadic bool ) {
buf . WriteByte ( '(' )
for i , n := 0 , t . Len ( ) ; i < n ; i ++ {
if i > 0 {
buf . WriteString ( ", " )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
typ := t . At ( i ) . Type ( )
if variadic && i + 1 == n {
buf . WriteString ( "..." )
typ = typ . ( * types . Slice ) . Elem ( )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
w . writeType ( buf , typ )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
buf . WriteByte ( ')' )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) typeString ( typ types . Type ) string {
var buf bytes . Buffer
w . writeType ( & buf , typ )
return buf . String ( )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) signatureString ( sig * types . Signature ) string {
var buf bytes . Buffer
w . writeSignature ( & buf , sig )
return buf . String ( )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitObj ( obj types . Object ) {
switch obj := obj . ( type ) {
case * types . Const :
w . emitf ( "const %s %s" , obj . Name ( ) , w . typeString ( obj . Type ( ) ) )
2013-09-06 12:01:01 -07:00
w . emitf ( "const %s = %s" , obj . Name ( ) , obj . Val ( ) )
2013-08-08 14:10:59 -07:00
case * types . Var :
w . emitf ( "var %s %s" , obj . Name ( ) , w . typeString ( obj . Type ( ) ) )
case * types . TypeName :
w . emitType ( obj )
case * types . Func :
w . emitFunc ( obj )
default :
panic ( "unknown object: " + obj . String ( ) )
2012-01-25 17:47:57 -08:00
}
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitType ( obj * types . TypeName ) {
name := obj . Name ( )
typ := obj . Type ( )
switch typ := typ . Underlying ( ) . ( type ) {
case * types . Struct :
w . emitStructType ( name , typ )
case * types . Interface :
w . emitIfaceType ( name , typ )
return // methods are handled by emitIfaceType
default :
w . emitf ( "type %s %s" , name , w . typeString ( typ . Underlying ( ) ) )
}
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
// emit methods with value receiver
var methodNames map [ string ] bool
2015-04-07 11:04:01 -07:00
vset := types . NewMethodSet ( typ )
2013-08-08 14:10:59 -07:00
for i , n := 0 , vset . Len ( ) ; i < n ; i ++ {
m := vset . At ( i )
2015-04-07 11:04:01 -07:00
if m . Obj ( ) . Exported ( ) {
2013-08-08 14:10:59 -07:00
w . emitMethod ( m )
if methodNames == nil {
methodNames = make ( map [ string ] bool )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
methodNames [ m . Obj ( ) . Name ( ) ] = true
2012-01-25 17:47:57 -08:00
}
}
2012-01-29 21:04:13 -08:00
2013-08-08 14:10:59 -07:00
// emit methods with pointer receiver; exclude
// methods that we have emitted already
// (the method set of *T includes the methods of T)
2015-04-07 11:04:01 -07:00
pset := types . NewMethodSet ( types . NewPointer ( typ ) )
2013-08-08 14:10:59 -07:00
for i , n := 0 , pset . Len ( ) ; i < n ; i ++ {
m := pset . At ( i )
2015-04-07 11:04:01 -07:00
if m . Obj ( ) . Exported ( ) && ! methodNames [ m . Obj ( ) . Name ( ) ] {
2013-08-08 14:10:59 -07:00
w . emitMethod ( m )
}
2012-01-25 17:47:57 -08:00
}
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitStructType ( name string , typ * types . Struct ) {
2012-01-25 17:47:57 -08:00
typeStruct := fmt . Sprintf ( "type %s struct" , name )
2013-08-08 14:10:59 -07:00
w . emitf ( typeStruct )
defer w . pushScope ( typeStruct ) ( )
for i := 0 ; i < typ . NumFields ( ) ; i ++ {
f := typ . Field ( i )
2015-04-07 11:04:01 -07:00
if ! f . Exported ( ) {
2013-08-08 14:10:59 -07:00
continue
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
typ := f . Type ( )
if f . Anonymous ( ) {
w . emitf ( "embedded %s" , w . typeString ( typ ) )
continue
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
w . emitf ( "%s %s" , f . Name ( ) , w . typeString ( typ ) )
2012-01-25 17:47:57 -08:00
}
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitIfaceType ( name string , typ * types . Interface ) {
pop := w . pushScope ( "type " + name + " interface" )
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
var methodNames [ ] string
complete := true
2015-04-07 11:04:01 -07:00
mset := types . NewMethodSet ( typ )
2013-08-08 14:10:59 -07:00
for i , n := 0 , mset . Len ( ) ; i < n ; i ++ {
m := mset . At ( i ) . Obj ( ) . ( * types . Func )
2015-04-07 11:04:01 -07:00
if ! m . Exported ( ) {
2013-08-08 14:10:59 -07:00
complete = false
continue
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
methodNames = append ( methodNames , m . Name ( ) )
w . emitf ( "%s%s" , m . Name ( ) , w . signatureString ( m . Type ( ) . ( * types . Signature ) ) )
2012-01-25 17:47:57 -08:00
}
2012-01-29 21:04:13 -08:00
2012-09-18 15:57:03 -04:00
if ! complete {
// The method set has unexported methods, so all the
// implementations are provided by the same package,
// so the method set can be extended. Instead of recording
// the full set of names (below), record only that there were
// unexported methods. (If the interface shrinks, we will notice
2013-08-08 14:10:59 -07:00
// because a method signature emitted during the last loop
2012-09-18 15:57:03 -04:00
// will disappear.)
2013-08-08 14:10:59 -07:00
w . emitf ( "unexported methods" )
2012-09-18 15:57:03 -04:00
}
2013-08-08 14:10:59 -07:00
2012-01-25 17:47:57 -08:00
pop ( )
2012-09-18 15:57:03 -04:00
if ! complete {
return
}
2013-08-08 14:10:59 -07:00
if len ( methodNames ) == 0 {
w . emitf ( "type %s interface {}" , name )
2012-02-21 07:37:25 +01:00
return
}
2012-01-25 17:47:57 -08:00
2013-08-08 14:10:59 -07:00
sort . Strings ( methodNames )
w . emitf ( "type %s interface { %s }" , name , strings . Join ( methodNames , ", " ) )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitFunc ( f * types . Func ) {
sig := f . Type ( ) . ( * types . Signature )
if sig . Recv ( ) != nil {
panic ( "method considered a regular function: " + f . String ( ) )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
w . emitf ( "func %s%s" , f . Name ( ) , w . signatureString ( sig ) )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitMethod ( m * types . Selection ) {
sig := m . Type ( ) . ( * types . Signature )
recv := sig . Recv ( ) . Type ( )
2014-05-02 13:17:55 -07:00
// report exported methods with unexported receiver base type
2013-08-08 14:10:59 -07:00
if true {
base := recv
if p , _ := recv . ( * types . Pointer ) ; p != nil {
base = p . Elem ( )
}
2015-04-07 11:04:01 -07:00
if obj := base . ( * types . Named ) . Obj ( ) ; ! obj . Exported ( ) {
2013-08-08 14:10:59 -07:00
log . Fatalf ( "exported method with unexported receiver base type: %s" , m )
2012-01-25 17:47:57 -08:00
}
}
2013-08-08 14:10:59 -07:00
w . emitf ( "method (%s) %s%s" , w . typeString ( recv ) , m . Obj ( ) . Name ( ) , w . signatureString ( sig ) )
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func ( w * Walker ) emitf ( format string , args ... interface { } ) {
f := strings . Join ( w . scope , ", " ) + ", " + fmt . Sprintf ( format , args ... )
if strings . Contains ( f , "\n" ) {
panic ( "feature contains newlines: " + f )
2012-01-25 17:47:57 -08:00
}
2013-01-28 16:45:45 -08:00
2012-01-25 17:47:57 -08:00
if _ , dup := w . features [ f ] ; dup {
panic ( "duplicate feature inserted: " + f )
}
w . features [ f ] = true
2013-08-08 14:10:59 -07:00
2012-01-25 17:47:57 -08:00
if * verbose {
log . Printf ( "feature: %s" , f )
}
}