2012-01-25 18:47:57 -07: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.
2012-01-31 08:38:07 -07:00
// Api computes the exported API of a set of Go packages.
2012-02-09 16:05:26 -07:00
//
// BUG(bradfitz): Note that this tool is only currently suitable
// for use on the Go standard library, not arbitrary packages.
// Once the Go AST has type information, this tool will be more
// reliable without hard-coded hacks throughout.
2012-01-25 18:47:57 -07:00
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/printer"
"go/token"
2012-10-30 04:23:44 -06:00
"io"
2012-01-25 18:47:57 -07:00
"io/ioutil"
"log"
"os"
"os/exec"
2012-01-29 22:04:13 -07:00
"path"
2012-01-25 18:47:57 -07:00
"path/filepath"
2012-10-30 06:12:59 -06:00
"regexp"
2012-06-08 11:44:13 -06:00
"runtime"
2012-01-25 18:47:57 -07:00
"sort"
2012-01-29 22:04:13 -07:00
"strconv"
2012-01-25 18:47:57 -07:00
"strings"
)
// Flags
var (
2012-05-22 19:41:20 -06:00
// TODO(bradfitz): once Go 1.1 comes out, allow the -c flag to take a comma-separated
// list of files, rather than just one.
2012-10-03 19:35:17 -06:00
checkFile = flag . String ( "c" , "" , "optional filename to check API against" )
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 18:47:57 -07:00
)
2012-05-23 14:45:53 -06:00
// contexts are the default contexts which are scanned, unless
// overridden by the -contexts flag.
2012-02-07 19:13:11 -07: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" } ,
2012-06-04 01:21:58 -06:00
{ GOOS : "linux" , GOARCH : "arm" } ,
2012-02-07 19:13:11 -07: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" } ,
2012-06-01 19:42:36 -06:00
{ GOOS : "freebsd" , GOARCH : "amd64" } ,
{ GOOS : "freebsd" , GOARCH : "386" } ,
2012-02-07 19:13:11 -07:00
}
func contextName ( c * build . Context ) string {
s := c . GOOS + "-" + c . GOARCH
if c . CgoEnabled {
return s + "-cgo"
}
return s
}
2012-05-23 14:45:53 -06: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 ) )
}
}
2012-01-25 18:47:57 -07:00
func main ( ) {
flag . Parse ( )
2012-09-24 19:35:20 -06:00
if ! strings . Contains ( runtime . Version ( ) , "weekly" ) && ! strings . Contains ( runtime . Version ( ) , "devel" ) {
2012-06-08 11:44:13 -06:00
if * nextFile != "" {
fmt . Printf ( "Go version is %q, ignoring -next %s\n" , runtime . Version ( ) , * nextFile )
* nextFile = ""
}
}
2012-05-23 14:45:53 -06:00
if * forceCtx != "" {
setContexts ( )
}
for _ , c := range contexts {
c . Compiler = build . Default . Compiler
}
2012-01-25 18:47:57 -07:00
var pkgs [ ] string
if flag . NArg ( ) > 0 {
pkgs = flag . Args ( )
} else {
stds , err := exec . Command ( "go" , "list" , "std" ) . Output ( )
if err != nil {
log . Fatal ( err )
}
pkgs = strings . Fields ( string ( stds ) )
}
2012-02-07 19:13:11 -07:00
var featureCtx = make ( map [ string ] map [ string ] bool ) // feature -> context name -> true
for _ , context := range contexts {
w := NewWalker ( )
w . context = context
for _ , pkg := range pkgs {
w . wantedPkg [ pkg ] = true
}
for _ , pkg := range pkgs {
if strings . HasPrefix ( pkg , "cmd/" ) ||
strings . HasPrefix ( pkg , "exp/" ) ||
strings . HasPrefix ( pkg , "old/" ) {
continue
}
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
if fi , err := os . Stat ( filepath . Join ( w . root , pkg ) ) ; err != nil || ! fi . IsDir ( ) {
2012-02-07 19:13:11 -07:00
log . Fatalf ( "no source in tree for package %q" , pkg )
}
w . WalkPackage ( pkg )
}
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 22:04:13 -07:00
}
2012-01-25 18:47:57 -07:00
2012-02-07 19:13:11 -07:00
var features [ ] string
for f , cmap := range featureCtx {
if len ( cmap ) == len ( contexts ) {
features = append ( features , f )
2012-01-25 18:47:57 -07:00
continue
}
2012-02-07 19:13:11 -07: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 18:47:57 -07:00
}
}
2012-05-22 19:41:20 -06:00
fail := false
defer func ( ) {
if fail {
os . Exit ( 1 )
}
} ( )
2012-01-25 18:47:57 -07:00
bw := bufio . NewWriter ( os . Stdout )
defer bw . Flush ( )
2012-05-22 19:41:20 -06:00
if * checkFile == "" {
for _ , f := range features {
fmt . Fprintf ( bw , "%s\n" , f )
2012-01-25 18:47:57 -07:00
}
2012-05-22 19:41:20 -06:00
return
}
2012-10-30 04:23:44 -06:00
required := fileFeatures ( * checkFile )
optional := fileFeatures ( * nextFile )
exception := fileFeatures ( * exceptFile )
fail = ! compareAPI ( bw , features , required , optional , exception )
}
2012-05-22 19:41:20 -06:00
2012-10-30 06:12:59 -06: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 , "" )
}
2012-10-30 04:23:44 -06:00
func compareAPI ( w io . Writer , features , required , optional , exception [ ] string ) ( ok bool ) {
ok = true
2012-05-22 19:41:20 -06:00
2012-10-30 06:12:59 -06:00
optionalSet := set ( optional )
exceptionSet := set ( exception )
featureSet := set ( features )
2012-10-30 04:23:44 -06:00
sort . Strings ( features )
sort . Strings ( required )
2012-10-03 19:35:17 -06:00
2012-05-22 19:41:20 -06: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 06:12:59 -06:00
case len ( features ) == 0 || ( len ( required ) > 0 && required [ 0 ] < features [ 0 ] ) :
2012-10-03 19:35:17 -06:00
feature := take ( & required )
2012-10-30 04:23:44 -06:00
if exceptionSet [ feature ] {
fmt . Fprintf ( w , "~%s\n" , feature )
2012-10-30 06:12:59 -06:00
} else if featureSet [ featureWithoutContext ( feature ) ] {
// okay.
2012-10-03 19:35:17 -06:00
} else {
2012-10-30 04:23:44 -06:00
fmt . Fprintf ( w , "-%s\n" , feature )
ok = false // broke compatibility
2012-10-03 19:35:17 -06:00
}
2012-10-30 06:12:59 -06:00
case len ( required ) == 0 || ( len ( features ) > 0 && required [ 0 ] > features [ 0 ] ) :
2012-05-22 19:41:20 -06:00
newFeature := take ( & features )
2012-10-30 04:23:44 -06:00
if optionalSet [ newFeature ] {
2012-05-22 19:41:20 -06: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 04:23:44 -06:00
delete ( optionalSet , newFeature )
2012-05-22 19:41:20 -06:00
} else {
2012-10-30 04:23:44 -06:00
fmt . Fprintf ( w , "+%s\n" , newFeature )
2012-05-22 19:41:20 -06:00
if ! * allowNew {
2012-10-30 04:23:44 -06:00
ok = false // we're in lock-down mode for next release
2012-05-22 19:41:20 -06:00
}
2012-01-25 18:47:57 -07:00
}
2012-05-22 19:41:20 -06:00
default :
take ( & required )
take ( & features )
2012-01-25 18:47:57 -07:00
}
}
2012-05-22 19:41:20 -06:00
2012-09-26 23:39:56 -06:00
// In next file, but not in API.
2012-05-22 19:41:20 -06:00
var missing [ ] string
2012-10-30 04:23:44 -06:00
for feature := range optionalSet {
2012-05-22 19:41:20 -06:00
missing = append ( missing , feature )
}
sort . Strings ( missing )
for _ , feature := range missing {
2012-10-30 04:23:44 -06:00
fmt . Fprintf ( w , "±%s\n" , feature )
2012-05-22 19:41:20 -06:00
}
2012-10-30 04:23:44 -06:00
return
2012-05-22 19:41:20 -06:00
}
func fileFeatures ( filename string ) [ ] string {
2012-10-30 04:23:44 -06:00
if filename == "" {
return nil
}
2012-05-22 19:41:20 -06:00
bs , err := ioutil . ReadFile ( filename )
if err != nil {
log . Fatalf ( "Error reading file %s: %v" , filename , err )
}
2012-06-08 11:44:13 -06:00
text := strings . TrimSpace ( string ( bs ) )
if text == "" {
return nil
}
return strings . Split ( text , "\n" )
2012-01-25 18:47:57 -07:00
}
2012-01-29 22:04:13 -07:00
// pkgSymbol represents a symbol in a package
type pkgSymbol struct {
pkg string // "net/http"
symbol string // "RoundTripper"
}
2012-11-13 10:59:46 -07:00
var fset = token . NewFileSet ( )
2012-01-25 18:47:57 -07:00
type Walker struct {
2012-02-07 19:13:11 -07:00
context * build . Context
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
root string
2012-01-29 22:04:13 -07:00
scope [ ] string
features map [ string ] bool // set
lastConstType string
curPackageName string
curPackage * ast . Package
2012-02-09 16:05:26 -07:00
prevConstType map [ pkgSymbol ] string
constDep map [ string ] string // key's const identifier has type of future value const identifier
2012-01-29 22:04:13 -07:00
packageState map [ string ] loadState
interfaces map [ pkgSymbol ] * ast . InterfaceType
2012-02-20 23:37:25 -07:00
functionTypes map [ pkgSymbol ] string // symbol => return type
selectorFullPkg map [ string ] string // "http" => "net/http", updated by imports
wantedPkg map [ string ] bool // packages requested on the command line
2012-01-25 18:47:57 -07:00
}
func NewWalker ( ) * Walker {
return & Walker {
2012-01-29 22:04:13 -07:00
features : make ( map [ string ] bool ) ,
packageState : make ( map [ string ] loadState ) ,
interfaces : make ( map [ pkgSymbol ] * ast . InterfaceType ) ,
2012-02-20 23:37:25 -07:00
functionTypes : make ( map [ pkgSymbol ] string ) ,
2012-01-29 22:04:13 -07:00
selectorFullPkg : make ( map [ string ] string ) ,
wantedPkg : make ( map [ string ] bool ) ,
2012-02-09 16:05:26 -07:00
prevConstType : make ( map [ pkgSymbol ] string ) ,
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
root : filepath . Join ( build . Default . GOROOT , "src/pkg" ) ,
2012-01-25 18:47:57 -07:00
}
}
2012-01-29 22:04:13 -07:00
// loadState is the state of a package's parsing.
type loadState int
const (
notLoaded loadState = iota
loading
loaded
)
2012-01-25 18:47:57 -07:00
func ( w * Walker ) Features ( ) ( fs [ ] string ) {
for f := range w . features {
fs = append ( fs , f )
}
sort . Strings ( fs )
return
}
2012-01-29 22:04:13 -07:00
// fileDeps returns the imports in a file.
func fileDeps ( f * ast . File ) ( pkgs [ ] string ) {
for _ , is := range f . Imports {
fpkg , err := strconv . Unquote ( is . Path . Value )
if err != nil {
log . Fatalf ( "error unquoting import string %q: %v" , is . Path . Value , err )
}
if fpkg != "C" {
pkgs = append ( pkgs , fpkg )
}
}
return
}
// WalkPackage walks all files in package `name'.
// WalkPackage does nothing if the package has already been loaded.
func ( w * Walker ) WalkPackage ( name string ) {
switch w . packageState [ name ] {
case loading :
log . Fatalf ( "import cycle loading package %q?" , name )
case loaded :
return
}
w . packageState [ name ] = loading
defer func ( ) {
w . packageState [ name ] = loaded
} ( )
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
dir := filepath . Join ( w . root , filepath . FromSlash ( name ) )
2012-01-25 18:47:57 -07:00
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
ctxt := w . context
if ctxt == nil {
ctxt = & build . Default
2012-02-07 19:13:11 -07:00
}
go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package
This is an API change, but one I have been promising would
happen when it was clear what the go command needed.
This is basically a complete replacement of what used to be here.
build.Tree is gone.
build.DirInfo is expanded and now called build.Package.
build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.
build.ScanDir is now build.ImportDir.
build.FindTree+build.ScanDir is now build.Import.
The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH. They will come back
with less information in the Package, but they will still work.
The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used. Path is gone, so it can no longer be misused. (Fixes issue 2749.)
This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.
R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
2012-03-01 10:12:09 -07:00
info , err := ctxt . ImportDir ( dir , 0 )
2012-01-25 18:47:57 -07:00
if err != nil {
2012-02-07 19:13:11 -07:00
if strings . Contains ( err . Error ( ) , "no Go source files" ) {
return
}
2012-01-25 18:47:57 -07:00
log . Fatalf ( "pkg %q, dir %q: ScanDir: %v" , name , dir , err )
}
apkg := & ast . Package {
Files : make ( map [ string ] * ast . File ) ,
}
files := append ( append ( [ ] string { } , info . GoFiles ... ) , info . CgoFiles ... )
for _ , file := range files {
2012-11-13 10:59:46 -07:00
f , err := parser . ParseFile ( fset , filepath . Join ( dir , file ) , nil , 0 )
2012-01-25 18:47:57 -07:00
if err != nil {
log . Fatalf ( "error parsing package %s, file %s: %v" , name , file , err )
}
apkg . Files [ file ] = f
2012-01-29 22:04:13 -07:00
for _ , dep := range fileDeps ( f ) {
w . WalkPackage ( dep )
}
2012-01-25 18:47:57 -07:00
}
2012-03-17 12:20:46 -06:00
if * verbose {
log . Printf ( "package %s" , name )
}
2012-01-29 22:04:13 -07:00
pop := w . pushScope ( "pkg " + name )
defer pop ( )
2012-01-25 18:47:57 -07:00
w . curPackageName = name
w . curPackage = apkg
2012-02-09 16:05:26 -07:00
w . constDep = map [ string ] string { }
2012-01-29 22:04:13 -07:00
for _ , afile := range apkg . Files {
w . recordTypes ( afile )
}
2012-02-20 23:37:25 -07:00
// Register all function declarations first.
for _ , afile := range apkg . Files {
for _ , di := range afile . Decls {
if d , ok := di . ( * ast . FuncDecl ) ; ok {
w . peekFuncDecl ( d )
}
}
}
2012-01-29 22:04:13 -07:00
for _ , afile := range apkg . Files {
w . walkFile ( afile )
2012-01-25 18:47:57 -07:00
}
2012-02-09 16:05:26 -07:00
w . resolveConstantDeps ( )
2012-01-25 18:47:57 -07:00
// Now that we're done walking types, vars and consts
// in the *ast.Package, use go/doc to do the rest
// (functions and methods). This is done here because
// go/doc is destructive. We can't use the
// *ast.Package after this.
2012-01-31 10:48:10 -07:00
dpkg := doc . New ( apkg , name , doc . AllMethods )
2012-01-25 18:47:57 -07:00
for _ , t := range dpkg . Types {
// Move funcs up to the top-level, not hiding in the Types.
dpkg . Funcs = append ( dpkg . Funcs , t . Funcs ... )
for _ , m := range t . Methods {
w . walkFuncDecl ( m . Decl )
}
}
for _ , f := range dpkg . Funcs {
w . walkFuncDecl ( f . Decl )
}
}
// 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 ]
}
}
2012-01-29 22:04:13 -07:00
func ( w * Walker ) recordTypes ( file * ast . File ) {
for _ , di := range file . Decls {
switch d := di . ( type ) {
case * ast . GenDecl :
switch d . Tok {
case token . TYPE :
for _ , sp := range d . Specs {
ts := sp . ( * ast . TypeSpec )
name := ts . Name . Name
if ast . IsExported ( name ) {
if it , ok := ts . Type . ( * ast . InterfaceType ) ; ok {
w . noteInterface ( name , it )
}
}
}
}
}
}
}
func ( w * Walker ) walkFile ( file * ast . File ) {
2012-01-25 18:47:57 -07:00
// Not entering a scope here; file boundaries aren't interesting.
for _ , di := range file . Decls {
switch d := di . ( type ) {
case * ast . GenDecl :
switch d . Tok {
case token . IMPORT :
2012-01-29 22:04:13 -07:00
for _ , sp := range d . Specs {
is := sp . ( * ast . ImportSpec )
fpath , err := strconv . Unquote ( is . Path . Value )
if err != nil {
log . Fatal ( err )
}
name := path . Base ( fpath )
if is . Name != nil {
name = is . Name . Name
}
w . selectorFullPkg [ name ] = fpath
}
2012-01-25 18:47:57 -07:00
case token . CONST :
for _ , sp := range d . Specs {
w . walkConst ( sp . ( * ast . ValueSpec ) )
}
case token . TYPE :
for _ , sp := range d . Specs {
w . walkTypeSpec ( sp . ( * ast . TypeSpec ) )
}
case token . VAR :
for _ , sp := range d . Specs {
w . walkVar ( sp . ( * ast . ValueSpec ) )
}
default :
log . Fatalf ( "unknown token type %d in GenDecl" , d . Tok )
}
case * ast . FuncDecl :
// Ignore. Handled in subsequent pass, by go/doc.
default :
log . Printf ( "unhandled %T, %#v\n" , di , di )
2012-11-13 10:59:46 -07:00
printer . Fprint ( os . Stderr , fset , di )
2012-01-25 18:47:57 -07:00
os . Stderr . Write ( [ ] byte ( "\n" ) )
}
}
}
var constType = map [ token . Token ] string {
token . INT : "ideal-int" ,
token . FLOAT : "ideal-float" ,
token . STRING : "ideal-string" ,
token . CHAR : "ideal-char" ,
token . IMAG : "ideal-imag" ,
}
var varType = map [ token . Token ] string {
token . INT : "int" ,
token . FLOAT : "float64" ,
token . STRING : "string" ,
token . CHAR : "rune" ,
token . IMAG : "complex128" ,
}
var errTODO = errors . New ( "TODO" )
func ( w * Walker ) constValueType ( vi interface { } ) ( string , error ) {
switch v := vi . ( type ) {
case * ast . BasicLit :
litType , ok := constType [ v . Kind ]
if ! ok {
return "" , fmt . Errorf ( "unknown basic literal kind %#v" , v )
}
return litType , nil
case * ast . UnaryExpr :
return w . constValueType ( v . X )
case * ast . SelectorExpr :
2012-02-09 16:05:26 -07:00
lhs := w . nodeString ( v . X )
rhs := w . nodeString ( v . Sel )
pkg , ok := w . selectorFullPkg [ lhs ]
if ! ok {
return "" , fmt . Errorf ( "unknown constant reference; unknown package in expression %s.%s" , lhs , rhs )
}
if t , ok := w . prevConstType [ pkgSymbol { pkg , rhs } ] ; ok {
return t , nil
}
return "" , fmt . Errorf ( "unknown constant reference to %s.%s" , lhs , rhs )
2012-01-25 18:47:57 -07:00
case * ast . Ident :
if v . Name == "iota" {
return "ideal-int" , nil // hack.
}
if v . Name == "false" || v . Name == "true" {
2012-02-18 18:31:19 -07:00
return "bool" , nil
2012-01-25 18:47:57 -07:00
}
if v . Name == "intSize" && w . curPackageName == "strconv" {
// Hack.
return "ideal-int" , nil
}
2012-02-09 16:05:26 -07:00
if t , ok := w . prevConstType [ pkgSymbol { w . curPackageName , v . Name } ] ; ok {
2012-01-25 18:47:57 -07:00
return t , nil
}
2012-02-09 16:05:26 -07:00
return constDepPrefix + v . Name , nil
2012-01-25 18:47:57 -07:00
case * ast . BinaryExpr :
2012-11-08 09:34:54 -07:00
switch v . Op {
case token . EQL , token . LSS , token . GTR , token . NOT , token . NEQ , token . LEQ , token . GEQ :
return "bool" , nil
}
2012-01-25 18:47:57 -07:00
left , err := w . constValueType ( v . X )
if err != nil {
return "" , err
}
right , err := w . constValueType ( v . Y )
if err != nil {
return "" , err
}
if left != right {
2012-02-09 16:05:26 -07:00
// TODO(bradfitz): encode the real rules here,
// rather than this mess.
2012-01-25 18:47:57 -07:00
if left == "ideal-int" && right == "ideal-float" {
return "ideal-float" , nil // math.Log2E
}
if left == "ideal-char" && right == "ideal-int" {
return "ideal-int" , nil // math/big.MaxBase
}
if left == "ideal-int" && right == "ideal-char" {
return "ideal-int" , nil // text/scanner.GoWhitespace
}
if left == "ideal-int" && right == "Duration" {
// Hack, for package time.
return "Duration" , nil
}
2012-02-09 16:05:26 -07:00
if left == "ideal-int" && ! strings . HasPrefix ( right , "ideal-" ) {
return right , nil
}
if right == "ideal-int" && ! strings . HasPrefix ( left , "ideal-" ) {
return left , nil
}
if strings . HasPrefix ( left , constDepPrefix ) && strings . HasPrefix ( right , constDepPrefix ) {
// Just pick one.
// e.g. text/scanner GoTokens const-dependency:ScanIdents, const-dependency:ScanFloats
return left , nil
}
2012-01-25 18:47:57 -07:00
return "" , fmt . Errorf ( "in BinaryExpr, unhandled type mismatch; left=%q, right=%q" , left , right )
}
return left , nil
case * ast . CallExpr :
// Not a call, but a type conversion.
return w . nodeString ( v . Fun ) , nil
case * ast . ParenExpr :
return w . constValueType ( v . X )
}
return "" , fmt . Errorf ( "unknown const value type %T" , vi )
}
func ( w * Walker ) varValueType ( vi interface { } ) ( string , error ) {
switch v := vi . ( type ) {
case * ast . BasicLit :
litType , ok := varType [ v . Kind ]
if ! ok {
return "" , fmt . Errorf ( "unknown basic literal kind %#v" , v )
}
return litType , nil
case * ast . CompositeLit :
return w . nodeString ( v . Type ) , nil
case * ast . FuncLit :
return w . nodeString ( w . namelessType ( v . Type ) ) , nil
case * ast . UnaryExpr :
if v . Op == token . AND {
typ , err := w . varValueType ( v . X )
return "*" + typ , err
}
return "" , fmt . Errorf ( "unknown unary expr: %#v" , v )
case * ast . SelectorExpr :
return "" , errTODO
case * ast . Ident :
node , _ , ok := w . resolveName ( v . Name )
if ! ok {
return "" , fmt . Errorf ( "unresolved identifier: %q" , v . Name )
}
return w . varValueType ( node )
case * ast . BinaryExpr :
left , err := w . varValueType ( v . X )
if err != nil {
return "" , err
}
right , err := w . varValueType ( v . Y )
if err != nil {
return "" , err
}
if left != right {
return "" , fmt . Errorf ( "in BinaryExpr, unhandled type mismatch; left=%q, right=%q" , left , right )
}
return left , nil
case * ast . ParenExpr :
return w . varValueType ( v . X )
case * ast . CallExpr :
2012-02-20 23:37:25 -07:00
var funSym pkgSymbol
if selnode , ok := v . Fun . ( * ast . SelectorExpr ) ; ok {
// assume it is not a method.
pkg , ok := w . selectorFullPkg [ w . nodeString ( selnode . X ) ]
if ! ok {
return "" , fmt . Errorf ( "not a package: %s" , w . nodeString ( selnode . X ) )
}
funSym = pkgSymbol { pkg , selnode . Sel . Name }
if retType , ok := w . functionTypes [ funSym ] ; ok {
if ast . IsExported ( retType ) && pkg != w . curPackageName {
// otherpkg.F returning an exported type from otherpkg.
return pkg + "." + retType , nil
} else {
return retType , nil
}
}
} else {
funSym = pkgSymbol { w . curPackageName , w . nodeString ( v . Fun ) }
if retType , ok := w . functionTypes [ funSym ] ; ok {
return retType , nil
}
2012-01-25 18:47:57 -07:00
}
// maybe a function call; maybe a conversion. Need to lookup type.
2012-03-11 18:55:15 -06:00
// TODO(bradfitz): this is a hack, but arguably most of this tool is,
// until the Go AST has type information.
nodeStr := w . nodeString ( v . Fun )
switch nodeStr {
case "string" , "[]byte" :
return nodeStr , nil
}
return "" , fmt . Errorf ( "not a known function %q" , nodeStr )
2012-01-25 18:47:57 -07:00
default :
return "" , fmt . Errorf ( "unknown const value type %T" , vi )
}
panic ( "unreachable" )
}
// resolveName finds a top-level node named name and returns the node
// v and its type t, if known.
func ( w * Walker ) resolveName ( name string ) ( v interface { } , t interface { } , ok bool ) {
for _ , file := range w . curPackage . Files {
for _ , di := range file . Decls {
switch d := di . ( type ) {
case * ast . GenDecl :
switch d . Tok {
case token . VAR :
for _ , sp := range d . Specs {
vs := sp . ( * ast . ValueSpec )
for i , vname := range vs . Names {
if vname . Name == name {
if len ( vs . Values ) > i {
return vs . Values [ i ] , vs . Type , true
}
return nil , vs . Type , true
}
}
}
}
}
}
}
return nil , nil , false
}
2012-02-09 16:05:26 -07:00
// constDepPrefix is a magic prefix that is used by constValueType
// and walkConst to signal that a type isn't known yet. These are
// resolved at the end of walking of a package's files.
const constDepPrefix = "const-dependency:"
2012-01-25 18:47:57 -07:00
func ( w * Walker ) walkConst ( vs * ast . ValueSpec ) {
for _ , ident := range vs . Names {
litType := ""
if vs . Type != nil {
litType = w . nodeString ( vs . Type )
} else {
litType = w . lastConstType
if vs . Values != nil {
if len ( vs . Values ) != 1 {
log . Fatalf ( "const %q, values: %#v" , ident . Name , vs . Values )
}
var err error
litType , err = w . constValueType ( vs . Values [ 0 ] )
if err != nil {
2012-11-08 09:34:54 -07:00
log . Fatalf ( "unknown kind in const %q (%T): %v" , ident . Name , vs . Values [ 0 ] , err )
2012-01-25 18:47:57 -07:00
}
}
}
2012-02-09 16:05:26 -07:00
if strings . HasPrefix ( litType , constDepPrefix ) {
dep := litType [ len ( constDepPrefix ) : ]
w . constDep [ ident . Name ] = dep
continue
}
2012-01-25 18:47:57 -07:00
if litType == "" {
log . Fatalf ( "unknown kind in const %q" , ident . Name )
}
w . lastConstType = litType
2012-02-09 16:05:26 -07:00
w . prevConstType [ pkgSymbol { w . curPackageName , ident . Name } ] = litType
if ast . IsExported ( ident . Name ) {
w . emitFeature ( fmt . Sprintf ( "const %s %s" , ident , litType ) )
}
}
}
func ( w * Walker ) resolveConstantDeps ( ) {
var findConstType func ( string ) string
findConstType = func ( ident string ) string {
if dep , ok := w . constDep [ ident ] ; ok {
return findConstType ( dep )
}
if t , ok := w . prevConstType [ pkgSymbol { w . curPackageName , ident } ] ; ok {
return t
}
return ""
}
for ident := range w . constDep {
if ! ast . IsExported ( ident ) {
continue
}
t := findConstType ( ident )
if t == "" {
log . Fatalf ( "failed to resolve constant %q" , ident )
}
w . emitFeature ( fmt . Sprintf ( "const %s %s" , ident , t ) )
2012-01-25 18:47:57 -07:00
}
}
func ( w * Walker ) walkVar ( vs * ast . ValueSpec ) {
for i , ident := range vs . Names {
if ! ast . IsExported ( ident . Name ) {
continue
}
typ := ""
if vs . Type != nil {
typ = w . nodeString ( vs . Type )
} else {
if len ( vs . Values ) == 0 {
log . Fatalf ( "no values for var %q" , ident . Name )
}
if len ( vs . Values ) > 1 {
log . Fatalf ( "more than 1 values in ValueSpec not handled, var %q" , ident . Name )
}
var err error
typ , err = w . varValueType ( vs . Values [ i ] )
if err != nil {
log . Fatalf ( "unknown type of variable %q, type %T, error = %v\ncode: %s" ,
ident . Name , vs . Values [ i ] , err , w . nodeString ( vs . Values [ i ] ) )
}
}
w . emitFeature ( fmt . Sprintf ( "var %s %s" , ident , typ ) )
}
}
func ( w * Walker ) nodeString ( node interface { } ) string {
if node == nil {
return ""
}
var b bytes . Buffer
2012-11-13 10:59:46 -07:00
printer . Fprint ( & b , fset , node )
2012-01-25 18:47:57 -07:00
return b . String ( )
}
func ( w * Walker ) nodeDebug ( node interface { } ) string {
if node == nil {
return ""
}
var b bytes . Buffer
2012-11-13 10:59:46 -07:00
ast . Fprint ( & b , fset , node , nil )
2012-01-25 18:47:57 -07:00
return b . String ( )
}
2012-01-29 22:04:13 -07:00
func ( w * Walker ) noteInterface ( name string , it * ast . InterfaceType ) {
w . interfaces [ pkgSymbol { w . curPackageName , name } ] = it
}
2012-01-25 18:47:57 -07:00
func ( w * Walker ) walkTypeSpec ( ts * ast . TypeSpec ) {
name := ts . Name . Name
if ! ast . IsExported ( name ) {
return
}
switch t := ts . Type . ( type ) {
case * ast . StructType :
w . walkStructType ( name , t )
case * ast . InterfaceType :
w . walkInterfaceType ( name , t )
default :
w . emitFeature ( fmt . Sprintf ( "type %s %s" , name , w . nodeString ( ts . Type ) ) )
}
}
func ( w * Walker ) walkStructType ( name string , t * ast . StructType ) {
typeStruct := fmt . Sprintf ( "type %s struct" , name )
w . emitFeature ( typeStruct )
pop := w . pushScope ( typeStruct )
defer pop ( )
for _ , f := range t . Fields . List {
typ := f . Type
for _ , name := range f . Names {
if ast . IsExported ( name . Name ) {
w . emitFeature ( fmt . Sprintf ( "%s %s" , name , w . nodeString ( w . namelessType ( typ ) ) ) )
}
}
if f . Names == nil {
switch v := typ . ( type ) {
case * ast . Ident :
if ast . IsExported ( v . Name ) {
w . emitFeature ( fmt . Sprintf ( "embedded %s" , v . Name ) )
}
case * ast . StarExpr :
switch vv := v . X . ( type ) {
case * ast . Ident :
if ast . IsExported ( vv . Name ) {
w . emitFeature ( fmt . Sprintf ( "embedded *%s" , vv . Name ) )
}
case * ast . SelectorExpr :
w . emitFeature ( fmt . Sprintf ( "embedded %s" , w . nodeString ( typ ) ) )
default :
2012-02-13 18:37:57 -07:00
log . Fatalf ( "unable to handle embedded starexpr before %T" , typ )
2012-01-25 18:47:57 -07:00
}
case * ast . SelectorExpr :
w . emitFeature ( fmt . Sprintf ( "embedded %s" , w . nodeString ( typ ) ) )
default :
log . Fatalf ( "unable to handle embedded %T" , typ )
}
}
}
}
2012-01-29 22:04:13 -07:00
// method is a method of an interface.
type method struct {
name string // "Read"
sig string // "([]byte) (int, error)", from funcSigString
}
2012-09-18 13:57:03 -06:00
// interfaceMethods returns the expanded list of exported methods for an interface.
// The boolean complete reports whether the list contains all methods (that is, the
// interface has no unexported methods).
2012-01-29 22:04:13 -07:00
// pkg is the complete package name ("net/http")
// iname is the interface name.
2012-09-18 13:57:03 -06:00
func ( w * Walker ) interfaceMethods ( pkg , iname string ) ( methods [ ] method , complete bool ) {
2012-01-29 22:04:13 -07:00
t , ok := w . interfaces [ pkgSymbol { pkg , iname } ]
if ! ok {
log . Fatalf ( "failed to find interface %s.%s" , pkg , iname )
}
2012-01-25 18:47:57 -07:00
2012-09-18 13:57:03 -06:00
complete = true
2012-01-25 18:47:57 -07:00
for _ , f := range t . Methods . List {
typ := f . Type
2012-01-29 22:04:13 -07:00
switch tv := typ . ( type ) {
case * ast . FuncType :
for _ , mname := range f . Names {
if ast . IsExported ( mname . Name ) {
ft := typ . ( * ast . FuncType )
methods = append ( methods , method {
name : mname . Name ,
sig : w . funcSigString ( ft ) ,
} )
2012-09-18 13:57:03 -06:00
} else {
complete = false
2012-01-29 22:04:13 -07:00
}
2012-01-25 18:47:57 -07:00
}
2012-01-29 22:04:13 -07:00
case * ast . Ident :
embedded := typ . ( * ast . Ident ) . Name
if embedded == "error" {
methods = append ( methods , method {
name : "Error" ,
sig : "() string" ,
} )
continue
}
if ! ast . IsExported ( embedded ) {
log . Fatalf ( "unexported embedded interface %q in exported interface %s.%s; confused" ,
embedded , pkg , iname )
}
2012-09-18 13:57:03 -06:00
m , c := w . interfaceMethods ( pkg , embedded )
methods = append ( methods , m ... )
complete = complete && c
2012-01-29 22:04:13 -07:00
case * ast . SelectorExpr :
lhs := w . nodeString ( tv . X )
rhs := w . nodeString ( tv . Sel )
fpkg , ok := w . selectorFullPkg [ lhs ]
if ! ok {
log . Fatalf ( "can't resolve selector %q in interface %s.%s" , lhs , pkg , iname )
}
2012-09-18 13:57:03 -06:00
m , c := w . interfaceMethods ( fpkg , rhs )
methods = append ( methods , m ... )
complete = complete && c
2012-01-29 22:04:13 -07:00
default :
log . Fatalf ( "unknown type %T in interface field" , typ )
2012-01-25 18:47:57 -07:00
}
}
2012-01-29 22:04:13 -07:00
return
}
func ( w * Walker ) walkInterfaceType ( name string , t * ast . InterfaceType ) {
methNames := [ ] string { }
pop := w . pushScope ( "type " + name + " interface" )
2012-09-18 13:57:03 -06:00
methods , complete := w . interfaceMethods ( w . curPackageName , name )
for _ , m := range methods {
2012-01-29 22:04:13 -07:00
methNames = append ( methNames , m . name )
w . emitFeature ( fmt . Sprintf ( "%s%s" , m . name , m . sig ) )
}
2012-09-18 13:57:03 -06: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
// because a method signature emitted during the last loop,
// will disappear.)
w . emitFeature ( "unexported methods" )
}
2012-01-25 18:47:57 -07:00
pop ( )
2012-09-18 13:57:03 -06:00
if ! complete {
return
}
2012-01-29 22:04:13 -07:00
sort . Strings ( methNames )
if len ( methNames ) == 0 {
2012-01-25 18:47:57 -07:00
w . emitFeature ( fmt . Sprintf ( "type %s interface {}" , name ) )
} else {
2012-01-29 22:04:13 -07:00
w . emitFeature ( fmt . Sprintf ( "type %s interface { %s }" , name , strings . Join ( methNames , ", " ) ) )
2012-01-25 18:47:57 -07:00
}
}
2012-02-20 23:37:25 -07:00
func ( w * Walker ) peekFuncDecl ( f * ast . FuncDecl ) {
if f . Recv != nil {
return
}
// Record return type for later use.
if f . Type . Results != nil && len ( f . Type . Results . List ) == 1 {
retType := w . nodeString ( w . namelessType ( f . Type . Results . List [ 0 ] . Type ) )
w . functionTypes [ pkgSymbol { w . curPackageName , f . Name . Name } ] = retType
}
}
2012-01-25 18:47:57 -07:00
func ( w * Walker ) walkFuncDecl ( f * ast . FuncDecl ) {
if ! ast . IsExported ( f . Name . Name ) {
return
}
if f . Recv != nil {
// Method.
recvType := w . nodeString ( f . Recv . List [ 0 ] . Type )
keep := ast . IsExported ( recvType ) ||
( strings . HasPrefix ( recvType , "*" ) &&
ast . IsExported ( recvType [ 1 : ] ) )
if ! keep {
return
}
w . emitFeature ( fmt . Sprintf ( "method (%s) %s%s" , recvType , f . Name . Name , w . funcSigString ( f . Type ) ) )
return
}
// Else, a function
w . emitFeature ( fmt . Sprintf ( "func %s%s" , f . Name . Name , w . funcSigString ( f . Type ) ) )
}
func ( w * Walker ) funcSigString ( ft * ast . FuncType ) string {
var b bytes . Buffer
2012-09-18 16:04:12 -06:00
writeField := func ( b * bytes . Buffer , f * ast . Field ) {
if n := len ( f . Names ) ; n > 1 {
for i := 0 ; i < n ; i ++ {
if i > 0 {
b . WriteString ( ", " )
}
b . WriteString ( w . nodeString ( w . namelessType ( f . Type ) ) )
}
} else {
b . WriteString ( w . nodeString ( w . namelessType ( f . Type ) ) )
}
}
2012-01-25 18:47:57 -07:00
b . WriteByte ( '(' )
if ft . Params != nil {
for i , f := range ft . Params . List {
if i > 0 {
b . WriteString ( ", " )
}
2012-09-18 16:04:12 -06:00
writeField ( & b , f )
2012-01-25 18:47:57 -07:00
}
}
b . WriteByte ( ')' )
if ft . Results != nil {
2012-09-18 16:04:12 -06:00
nr := 0
for _ , f := range ft . Results . List {
if n := len ( f . Names ) ; n > 1 {
nr += n
} else {
nr ++
}
}
if nr > 0 {
2012-01-25 18:47:57 -07:00
b . WriteByte ( ' ' )
if nr > 1 {
b . WriteByte ( '(' )
}
for i , f := range ft . Results . List {
if i > 0 {
b . WriteString ( ", " )
}
2012-09-18 16:04:12 -06:00
writeField ( & b , f )
2012-01-25 18:47:57 -07:00
}
if nr > 1 {
b . WriteByte ( ')' )
}
}
}
return b . String ( )
}
// namelessType returns a type node that lacks any variable names.
func ( w * Walker ) namelessType ( t interface { } ) interface { } {
ft , ok := t . ( * ast . FuncType )
if ! ok {
return t
}
return & ast . FuncType {
Params : w . namelessFieldList ( ft . Params ) ,
Results : w . namelessFieldList ( ft . Results ) ,
}
}
// namelessFieldList returns a deep clone of fl, with the cloned fields
// lacking names.
func ( w * Walker ) namelessFieldList ( fl * ast . FieldList ) * ast . FieldList {
fl2 := & ast . FieldList { }
if fl != nil {
for _ , f := range fl . List {
fl2 . List = append ( fl2 . List , w . namelessField ( f ) )
}
}
return fl2
}
// namelessField clones f, but not preserving the names of fields.
// (comments and tags are also ignored)
func ( w * Walker ) namelessField ( f * ast . Field ) * ast . Field {
return & ast . Field {
Type : f . Type ,
}
}
func ( w * Walker ) emitFeature ( feature string ) {
2012-01-29 22:04:13 -07:00
if ! w . wantedPkg [ w . curPackageName ] {
return
}
2012-01-25 18:47:57 -07:00
f := strings . Join ( w . scope , ", " ) + ", " + feature
if _ , dup := w . features [ f ] ; dup {
panic ( "duplicate feature inserted: " + f )
}
if strings . Contains ( f , "\n" ) {
// TODO: for now, just skip over the
// runtime.MemStatsType.BySize type, which this tool
// doesn't properly handle. It's pretty low-level,
// though, so not super important to protect against.
if strings . HasPrefix ( f , "pkg runtime" ) && strings . Contains ( f , "BySize [61]struct" ) {
return
}
panic ( "feature contains newlines: " + f )
}
w . features [ f ] = true
if * verbose {
log . Printf ( "feature: %s" , f )
}
}
func strListContains ( l [ ] string , s string ) bool {
for _ , v := range l {
if v == s {
return true
}
}
return false
}