2016-03-01 22:57:46 +00:00
// Copyright 2011 The Go Authors. All rights reserved.
2012-01-25 17:47:57 -08:00
// 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"
2019-03-05 16:52:48 -05:00
"encoding/json"
2012-01-25 17:47:57 -08:00
"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"
2020-03-20 15:15:35 -04:00
"sync"
2012-01-25 17:47:57 -08:00
)
2017-09-19 18:18:09 +09:00
func goCmd ( ) string {
var exeSuffix string
if runtime . GOOS == "windows" {
exeSuffix = ".exe"
}
path := filepath . Join ( runtime . GOROOT ( ) , "bin" , "go" + exeSuffix )
if _ , err := os . Stat ( path ) ; err == nil {
return path
}
return "go"
}
2012-01-25 17:47:57 -08:00
// 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 : "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" } ,
2019-04-20 14:39:33 +00:00
{ GOOS : "netbsd" , GOARCH : "arm64" , CgoEnabled : true } ,
{ GOOS : "netbsd" , GOARCH : "arm64" } ,
2013-05-21 21:47:32 +08:00
{ 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 {
2020-09-14 10:19:47 -04:00
s += "-cgo"
}
if c . Dir != "" {
s += fmt . Sprintf ( " [%s]" , c . Dir )
2012-02-07 18:13:11 -08:00
}
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
}
2020-03-20 15:15:35 -04:00
walkers := make ( [ ] * Walker , len ( contexts ) )
var wg sync . WaitGroup
for i , context := range contexts {
i , context := i , context
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
walkers [ i ] = NewWalker ( context , filepath . Join ( build . Default . GOROOT , "src" ) )
} ( )
2012-01-25 17:47:57 -08:00
}
2020-03-20 15:15:35 -04:00
wg . Wait ( )
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
2020-03-20 15:15:35 -04:00
for _ , w := range walkers {
pkgNames := w . stdPackages
if flag . NArg ( ) > 0 {
pkgNames = flag . Args ( )
}
2012-02-07 18:13:11 -08:00
2013-08-08 14:10:59 -07:00
for _ , name := range pkgNames {
2020-03-20 15:15:35 -04:00
pkg , err := w . Import ( name )
if _ , nogo := err . ( * build . NoGoError ) ; nogo {
2016-04-22 22:13:50 +00:00
continue
}
2020-03-20 15:15:35 -04:00
if err != nil {
log . Fatalf ( "Import(%q): %v" , name , err )
2012-02-07 18:13:11 -08:00
}
2020-03-20 15:15:35 -04:00
w . export ( pkg )
2012-02-07 18:13:11 -08:00
}
2013-08-08 14:10:59 -07:00
2020-03-20 15:15:35 -04:00
ctxName := contextName ( w . context )
2012-02-07 18:13:11 -08:00
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
}
2017-10-05 15:49:32 +02:00
comma := strings . Index ( f , "," )
2012-02-07 18:13:11 -08:00
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 ( ) {
all: clean up code with token.IsExported
A handful of packages were reimplementing IsExported, so use
token.IsExported instead. This caused the deps test to fail for net/rpc.
However, net/rpc deals with Go types, and go/token is light and fairly
low-level in terms of Go tooling packages, so that's okay.
While at it, replace all uses of ast.IsExported with token.IsExported.
This is more consistent, and also means that the import graphs are
leaner. A couple of files no longer need to import go/ast, for example.
We can't get rid of cmd/compile/internal/types.IsExported, as the
compiler can only depend on go/token as of Go 1.4. However, gc used
different implementations in a couple of places, so consolidate the use
of types.IsExported there.
Finally, we can't get rid of the copied IsExported implementation in
encoding/gob, as go/token depends on it as part of a test. That test
can't be an external test either, so there's no easy way to break the
import cycle.
Overall, this removes about forty lines of unnecessary code.
Change-Id: I86a475b7614261e6a7b0b153d5ca02b9f64a7b2d
Reviewed-on: https://go-review.googlesource.com/c/go/+/172037
Run-TryBot: Daniel Martà <mvdan@mvdan.cc>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2019-04-15 23:10:50 +09:00
if token . IsExported ( name ) {
2013-08-08 14:10:59 -07:00
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 , "" )
}
2020-04-02 22:44:25 -07:00
// portRemoved reports whether the given port-specific API feature is
// okay to no longer exist because its port was removed.
func portRemoved ( feature string ) bool {
return strings . Contains ( feature , "(darwin-386)" ) ||
strings . Contains ( feature , "(darwin-386-cgo)" )
}
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.
2020-04-02 22:44:25 -07:00
} else if portRemoved ( feature ) {
// okay.
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
}
2020-07-06 09:49:20 -04:00
// aliasReplacer applies type aliases to earlier API files,
// to avoid misleading negative results.
// This makes all the references to os.FileInfo in go1.txt
// be read as if they said fs.FileInfo, since os.FileInfo is now an alias.
// If there are many of these, we could do a more general solution,
// but for now the replacer is fine.
var aliasReplacer = strings . NewReplacer (
"os.FileInfo" , "fs.FileInfo" ,
"os.FileMode" , "fs.FileMode" ,
"os.PathError" , "fs.PathError" ,
)
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 )
}
2020-07-06 09:49:20 -04:00
s := string ( bs )
s = aliasReplacer . Replace ( s )
lines := strings . Split ( s , "\n" )
2014-12-05 14:04:17 -05:00
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 {
2020-03-20 15:15:35 -04:00
context * build . Context
root string
scope [ ] string
current * types . Package
features map [ string ] bool // set
imported map [ string ] * types . Package // packages already imported
stdPackages [ ] string // names, omitting "unsafe", internal, and vendored packages
importMap map [ string ] map [ string ] string // importer dir -> import path -> canonical path
importDir map [ string ] string // canonical import path -> dir
2012-01-25 17:47:57 -08:00
}
2013-08-08 14:10:59 -07:00
func NewWalker ( context * build . Context , root string ) * Walker {
2020-03-20 15:15:35 -04:00
w := & 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
}
2020-03-20 15:15:35 -04:00
w . loadImports ( )
return w
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
}
2018-10-04 16:46:22 +08:00
// Disable before debugging non-obvious errors from the type-checker.
2014-09-15 16:40:43 -07:00
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
2018-10-04 16:46:22 +08:00
// matter (the ones listed in the tags argument plus GOOS and GOARCH) are used.
2013-08-09 18:44:00 -04:00
// 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
2018-10-04 16:46:22 +08:00
// explicit on GOOS and GOARCH as global cache will use "all" cached packages for
// an indirect imported package. See https://github.com/golang/go/issues/21181
// for more detail.
tags = append ( tags , context . GOOS , context . GOARCH )
sort . Strings ( tags )
2013-08-09 18:44:00 -04:00
for _ , tag := range tags {
if ctags [ tag ] {
key += "," + tag
2018-10-04 16:46:22 +08:00
ctags [ tag ] = false
2013-08-09 18:44:00 -04:00
}
}
return key
}
2020-03-20 15:15:35 -04:00
type listImports struct {
stdPackages [ ] string // names, omitting "unsafe", internal, and vendored packages
importDir map [ string ] string // canonical import path → directory
importMap map [ string ] map [ string ] string // import path → canonical import path
}
var listCache sync . Map // map[string]listImports, keyed by contextName
2020-04-20 11:32:05 -04:00
// listSem is a semaphore restricting concurrent invocations of 'go list'.
var listSem = make ( chan semToken , runtime . GOMAXPROCS ( 0 ) )
type semToken struct { }
2020-03-20 15:15:35 -04:00
// loadImports populates w with information about the packages in the standard
// library and the packages they themselves import in w's build context.
//
2019-05-16 07:29:51 -04:00
// The source import path and expanded import path are identical except for vendored packages.
// For example, on return:
//
2020-03-20 15:15:35 -04:00
// w.importMap["math"] = "math"
// w.importDir["math"] = "<goroot>/src/math"
2019-05-16 07:29:51 -04:00
//
2020-03-20 15:15:35 -04:00
// w.importMap["golang.org/x/net/route"] = "vendor/golang.org/x/net/route"
// w.importDir["vendor/golang.org/x/net/route"] = "<goroot>/src/vendor/golang.org/x/net/route"
2019-05-16 07:29:51 -04:00
//
2020-03-20 15:15:35 -04:00
// Since the set of packages that exist depends on context, the result of
// loadImports also depends on context. However, to improve test running time
// the configuration for each environment is cached across runs.
func ( w * Walker ) loadImports ( ) {
if w . context == nil {
return // test-only Walker; does not use the import map
2019-03-05 16:52:48 -05:00
}
2020-03-20 15:15:35 -04:00
name := contextName ( w . context )
imports , ok := listCache . Load ( name )
if ! ok {
2020-04-20 11:32:05 -04:00
listSem <- semToken { }
defer func ( ) { <- listSem } ( )
2020-03-20 15:15:35 -04:00
cmd := exec . Command ( goCmd ( ) , "list" , "-e" , "-deps" , "-json" , "std" )
cmd . Env = listEnv ( w . context )
2020-09-14 10:19:47 -04:00
if w . context . Dir != "" {
cmd . Dir = w . context . Dir
}
2020-03-20 15:15:35 -04:00
out , err := cmd . CombinedOutput ( )
2019-05-16 07:29:51 -04:00
if err != nil {
2020-03-20 15:15:35 -04:00
log . Fatalf ( "loading imports: %v\n%s" , err , out )
}
var stdPackages [ ] string
importMap := make ( map [ string ] map [ string ] string )
importDir := make ( map [ string ] string )
dec := json . NewDecoder ( bytes . NewReader ( out ) )
for {
var pkg struct {
ImportPath , Dir string
ImportMap map [ string ] string
2020-09-14 10:19:47 -04:00
Standard bool
2020-03-20 15:15:35 -04:00
}
err := dec . Decode ( & pkg )
if err == io . EOF {
break
}
if err != nil {
log . Fatalf ( "go list: invalid output: %v" , err )
}
// - Package "unsafe" contains special signatures requiring
// extra care when printing them - ignore since it is not
// going to change w/o a language change.
2020-09-14 10:19:47 -04:00
// - Internal and vendored packages do not contribute to our
// API surface. (If we are running within the "std" module,
// vendored dependencies appear as themselves instead of
// their "vendor/" standard-library copies.)
2020-03-20 15:15:35 -04:00
// - 'go list std' does not include commands, which cannot be
// imported anyway.
2020-09-14 10:19:47 -04:00
if ip := pkg . ImportPath ; pkg . Standard && ip != "unsafe" && ! strings . HasPrefix ( ip , "vendor/" ) && ! internalPkg . MatchString ( ip ) {
2020-03-20 15:15:35 -04:00
stdPackages = append ( stdPackages , ip )
}
importDir [ pkg . ImportPath ] = pkg . Dir
if len ( pkg . ImportMap ) > 0 {
importMap [ pkg . Dir ] = make ( map [ string ] string , len ( pkg . ImportMap ) )
}
for k , v := range pkg . ImportMap {
importMap [ pkg . Dir ] [ k ] = v
}
2019-03-05 16:52:48 -05:00
}
2020-03-20 15:15:35 -04:00
sort . Strings ( stdPackages )
imports = listImports {
stdPackages : stdPackages ,
importMap : importMap ,
importDir : importDir ,
2019-05-16 07:29:51 -04:00
}
2020-03-20 15:15:35 -04:00
imports , _ = listCache . LoadOrStore ( name , imports )
2019-03-05 16:52:48 -05:00
}
2019-05-16 07:29:51 -04:00
2020-03-20 15:15:35 -04:00
li := imports . ( listImports )
w . stdPackages = li . stdPackages
w . importDir = li . importDir
w . importMap = li . importMap
}
// listEnv returns the process environment to use when invoking 'go list' for
// the given context.
func listEnv ( c * build . Context ) [ ] string {
if c == nil {
return os . Environ ( )
2019-05-16 07:29:51 -04:00
}
2020-03-20 15:15:35 -04:00
environ := append ( os . Environ ( ) ,
"GOOS=" + c . GOOS ,
"GOARCH=" + c . GOARCH )
if c . CgoEnabled {
environ = append ( environ , "CGO_ENABLED=1" )
} else {
environ = append ( environ , "CGO_ENABLED=0" )
2019-05-16 07:29:51 -04:00
}
2020-03-20 15:15:35 -04:00
return environ
2019-03-05 16:52:48 -05:00
}
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 ) {
2019-03-05 16:52:48 -05:00
return w . ImportFrom ( name , "" , 0 )
}
func ( w * Walker ) ImportFrom ( fromPath , fromDir string , mode types . ImportMode ) ( * types . Package , error ) {
name := fromPath
if canonical , ok := w . importMap [ fromDir ] [ fromPath ] ; ok {
name = canonical
}
2015-04-10 17:50:06 -07:00
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
// Determine package files.
2019-03-05 16:52:48 -05:00
dir := w . importDir [ name ]
if dir == "" {
dir = filepath . Join ( w . root , filepath . FromSlash ( name ) )
}
2013-08-08 14:10:59 -07:00
if fi , err := os . Stat ( dir ) ; err != nil || ! fi . IsDir ( ) {
2020-03-20 15:15:35 -04:00
log . Panicf ( "no source in tree for import %q (from import %s in %s): %v" , name , fromPath , fromDir , 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 {
2019-01-23 00:22:53 +08:00
return nil , err
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 ( ) ) )
go/constant: switch to floating-point representation when fractions become too large
Use two internal representations for Float values (similar to what is done
for Int values). Transparently switch to a big.Float representation when
big.Rat values become unwieldy. This is almost never needed for real-world
programs but it is trivial to create test cases that cannot be handled with
rational arithmetic alone.
As a consequence, the go/constant API semantics changes slightly: Until now,
a value could always be represented in its "smallest" form (e.g., float values
that happened to be integers would be represented as integers). Now, constant
Kind depends on how the value was created, rather than its actual value. (The
reason why we cannot automatically "normalize" values to their smallest form
anymore is because floating-point numbers are not exact in general; and thus
normalization is often not possible in the first place, or would throw away
precision when it is not desired.) This has repercussions as to how constant
Values are used go/types and required corresponding adjustments.
Details of the changes:
go/constant package:
- use big.Rat and big.Float values to represent floating-point values
(internal change)
- changed semantic of Value.Kind accordingly
- String now returns a short, human-readable form of a value
(this leads to better error messages in go/types)
- added ToInt, ToFloat, and ToComplex conversion functions
- added ExactString to obtain an exact string form of a value
go/types:
- adjusted and simplified implementation of representableConst
- adjusted various places where Value.Kind was expected to be "smallest"
by calling the respective ToInt/Float/Complex conversion functions
- enabled 5 disabled tests in stdlib_test.go that now work
api checker:
- print all constant values in a short human-readable form (floats are
printed in floating-point form), but also print an exact form if it
is different from the short form
- adjusted test golden file and go.1.1.text reference file
Fixes #11327.
Change-Id: I492b704aae5b0238e5b7cee13e18ffce61193587
Reviewed-on: https://go-review.googlesource.com/17360
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2015-12-02 17:41:39 -08:00
x := obj . Val ( )
short := x . String ( )
exact := x . ExactString ( )
if short == exact {
w . emitf ( "const %s = %s" , obj . Name ( ) , short )
} else {
w . emitf ( "const %s = %s // %s" , obj . Name ( ) , short , exact )
}
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 ( )
2020-07-06 09:49:20 -04:00
if obj . IsAlias ( ) {
w . emitf ( "type %s = %s" , name , w . typeString ( typ ) )
return
}
2013-08-08 14:10:59 -07:00
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 )
}
}