mirror of
https://github.com/golang/go
synced 2024-11-19 00:44:40 -07:00
go.tools/go/importer: fine-tuning of export format, test against std lib
(TBR adonovan) - provide version string rather than version number (more flexible) - match naming of tag constants with documentation (forth-coming) - more formats for constant values (1-byte booleans, short 64bit values, support for floating point numbers +123p+45) - more regular format for signatures: always print with type tag; this permits (future) sharing of identical signatures and possibly large export data size reduction - test export and import of all type-checked std library packages - compare import of all std library packages against corresponding import of gc-generated export data and verify that the packages match 100% (except for floating point values which are exact when coming from go/types, and possibly inexact when coming from gc) On average, export data generated by the importer is ~28% of the size of the gc-generated export data for corresponding packages of the std library: package size gc size archive/tar 2945 12030 24% archive/zip 4122 15796 26% bufio 1776 6171 28% bytes 2540 8006 31% compress/bzip2 146 419 34% compress/flate 2305 6356 36% compress/gzip 4262 17917 23% compress/lzw 336 832 40% compress/zlib 2503 9814 25% container/heap 255 527 48% container/list 653 2971 21% container/ring 246 707 34% crypto 368 1015 36% crypto/aes 219 499 43% crypto/cipher 809 1875 43% crypto/des 254 636 39% crypto/dsa 3324 15609 21% crypto/ecdsa 3833 18567 20% crypto/elliptic 3801 17417 21% crypto/hmac 205 459 44% crypto/md5 206 413 49% crypto/rand 3070 15047 20% crypto/rc4 180 443 40% crypto/rsa 4205 17941 23% crypto/sha1 208 414 50% crypto/sha256 272 555 49% crypto/sha512 274 555 49% crypto/subtle 247 1157 21% crypto/tls 13769 48330 28% crypto/x509 10716 40556 26% crypto/x509/pkix 5670 24616 23% database/sql 4227 15957 26% database/sql/driver 1239 2806 44% debug/dwarf 5165 12066 42% debug/elf 14205 36401 39% debug/goobj 992 2495 39% debug/gosym 1808 4798 37% debug/macho 4560 13173 34% debug/pe 3694 10530 35% encoding 261 387 67% encoding/ascii85 368 1541 23% encoding/asn1 479 1661 28% encoding/base32 608 2636 23% encoding/base64 610 2636 23% encoding/binary 1006 3591 28% encoding/csv 2287 8405 27% encoding/gob 9161 30565 29% encoding/hex 384 1331 28% encoding/json 6564 23334 28% encoding/pem 208 529 39% encoding/xml 7964 27220 29% errors 52 277 18% expvar 955 2978 32% flag 2475 18028 13% fmt 1141 3141 36% go/ast 11595 41019 28% go/build 3736 13920 26% go/doc 2647 9523 27% go/format 1310 5010 26% go/parser 4346 18525 23% go/printer 1737 6442 26% go/scanner 2360 8598 27% go/token 2290 7599 30% hash 215 595 36% hash/adler32 217 391 55% hash/crc32 399 1475 27% hash/crc64 324 1327 24% hash/fnv 338 3964 8% html 80 253 31% html/template 7981 37471 21% image 4270 18894 22% image/color 845 3154 26% image/color/palette 121 319 37% image/draw 947 4807 19% image/gif 1468 6611 22% image/jpeg 971 4534 21% image/png 880 4432 19% index/suffixarray 3702 13960 26% io 2049 6117 33% io/ioutil 2845 12060 23% log 2416 10241 23% log/syslog 3102 12208 25% math 2361 6008 39% math/big 3648 12250 29% math/cmplx 473 1542 30% math/rand 786 2002 39% mime 180 595 30% mime/multipart 2761 9338 29% net 10593 34352 30% net/http 24542 84065 29% net/http/cgi 12859 50970 25% net/http/cookiejar 2845 12429 22% net/http/fcgi 12331 49111 25% net/http/httptest 18953 75243 25% net/http/httputil 13994 54386 25% net/http/pprof 12153 49039 24% net/mail 1803 8599 20% net/rpc 18503 69044 26% net/rpc/jsonrpc 1063 2737 38% net/smtp 11775 46821 25% net/textproto 2684 8678 30% net/url 767 2886 26% os 5712 20293 28% os/exec 4705 18350 25% os/signal 125 330 37% os/user 234 720 32% path 231 770 30% path/filepath 2236 9721 23% reflect 4518 13467 33% regexp 3608 11167 32% regexp/syntax 2184 5157 42% runtime 1563 3407 45% runtime/cgo 36 66 54% runtime/debug 1714 8010 21% runtime/pprof 485 1358 35% runtime/race 38 43 88% sort 748 2658 28% strconv 949 2573 36% strings 1846 6220 29% sync 712 1970 36% sync/atomic 1089 2506 43% syscall 38975 79396 49% testing 3734 13626 27% testing/iotest 332 2378 13% testing/quick 4411 16434 26% text/scanner 1821 5349 34% text/tabwriter 1593 4937 32% text/template 6882 33329 20% text/template/parse 3946 12760 30% time 3031 9703 31% unicode 3148 8676 36% unicode/utf16 163 760 21% unicode/utf8 475 1122 42% total 463641 1644936 28% R=adonovan TBR=adonovan CC=golang-codereviews https://golang.org/cl/37250044
This commit is contained in:
parent
e405e03b16
commit
30645c4bc6
@ -23,30 +23,40 @@ const (
|
||||
|
||||
const (
|
||||
magic = "\n$$ exports $$\n"
|
||||
version = 0
|
||||
version = "v0"
|
||||
)
|
||||
|
||||
// Object and type tags. Must be < 0.
|
||||
const (
|
||||
// Packages
|
||||
packageTag = -(iota + 1)
|
||||
|
||||
// Objects
|
||||
_Package = -(iota + 1)
|
||||
_Const
|
||||
_Type
|
||||
_Var
|
||||
_Func
|
||||
constTag
|
||||
typeTag
|
||||
varTag
|
||||
funcTag
|
||||
|
||||
// Types
|
||||
_Basic
|
||||
_Array
|
||||
_Slice
|
||||
_Struct
|
||||
_Pointer
|
||||
_Tuple
|
||||
_Signature
|
||||
_Interface
|
||||
_Map
|
||||
_Chan
|
||||
_Named
|
||||
basicTag
|
||||
arrayTag
|
||||
sliceTag
|
||||
structTag
|
||||
pointerTag
|
||||
signatureTag
|
||||
interfaceTag
|
||||
mapTag
|
||||
chanTag
|
||||
namedTag
|
||||
|
||||
// Values
|
||||
intTag
|
||||
floatTag
|
||||
fractionTag
|
||||
complexTag
|
||||
stringTag
|
||||
falseTag
|
||||
trueTag
|
||||
)
|
||||
|
||||
// ExportData serializes the interface (exported package objects)
|
||||
@ -72,7 +82,7 @@ func ExportData(pkg *types.Package) []byte {
|
||||
defer p.tracef("\n")
|
||||
}
|
||||
|
||||
p.int(version)
|
||||
p.string(version)
|
||||
|
||||
p.pkg(pkg)
|
||||
|
||||
@ -117,7 +127,7 @@ func (p *exporter) pkg(pkg *types.Package) {
|
||||
p.pkgIndex[pkg] = len(p.pkgIndex)
|
||||
|
||||
// otherwise, write the package tag (< 0) and package data
|
||||
p.int(_Package)
|
||||
p.int(packageTag)
|
||||
p.string(pkg.Name())
|
||||
p.string(pkg.Path())
|
||||
}
|
||||
@ -130,67 +140,98 @@ func (p *exporter) obj(obj types.Object) {
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
p.int(_Const)
|
||||
p.int(constTag)
|
||||
p.string(obj.Name())
|
||||
p.typ(obj.Type())
|
||||
p.val(obj.Val())
|
||||
p.value(obj.Val())
|
||||
case *types.TypeName:
|
||||
p.int(_Type)
|
||||
p.int(typeTag)
|
||||
// name is written by corresponding named type
|
||||
p.typ(obj.Type().(*types.Named))
|
||||
case *types.Var:
|
||||
p.int(_Var)
|
||||
p.int(varTag)
|
||||
p.string(obj.Name())
|
||||
p.typ(obj.Type())
|
||||
case *types.Func:
|
||||
p.int(_Func)
|
||||
p.int(funcTag)
|
||||
p.string(obj.Name())
|
||||
p.signature(obj.Type().(*types.Signature))
|
||||
p.typ(obj.Type())
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected object type %T", obj))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) val(x exact.Value) {
|
||||
func (p *exporter) value(x exact.Value) {
|
||||
if trace {
|
||||
p.tracef("value { ")
|
||||
defer p.tracef("} ")
|
||||
}
|
||||
|
||||
kind := x.Kind()
|
||||
p.int(int(kind))
|
||||
switch kind {
|
||||
switch kind := x.Kind(); kind {
|
||||
case exact.Bool:
|
||||
p.bool(exact.BoolVal(x))
|
||||
tag := falseTag
|
||||
if exact.BoolVal(x) {
|
||||
tag = trueTag
|
||||
}
|
||||
p.int(tag)
|
||||
case exact.String:
|
||||
p.int(stringTag)
|
||||
p.string(exact.StringVal(x))
|
||||
case exact.Int:
|
||||
p.intVal(x)
|
||||
if i, ok := exact.Int64Val(x); ok {
|
||||
p.int(intTag)
|
||||
p.int64(i)
|
||||
return
|
||||
}
|
||||
p.int(floatTag)
|
||||
p.float(x)
|
||||
case exact.Float:
|
||||
p.floatVal(x)
|
||||
p.int(fractionTag)
|
||||
p.fraction(x)
|
||||
case exact.Complex:
|
||||
p.floatVal(exact.Real(x))
|
||||
p.floatVal(exact.Imag(x))
|
||||
p.int(complexTag)
|
||||
p.fraction(exact.Real(x))
|
||||
p.fraction(exact.Imag(x))
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected value kind %d", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) intVal(x exact.Value) {
|
||||
func (p *exporter) float(x exact.Value) {
|
||||
sign := exact.Sign(x)
|
||||
p.int(sign)
|
||||
if sign != 0 {
|
||||
p.bytes(exact.Bytes(x))
|
||||
if sign == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.ufloat(x)
|
||||
}
|
||||
|
||||
func (p *exporter) floatVal(x exact.Value) {
|
||||
p.intVal(exact.Num(x))
|
||||
if exact.Sign(x) != 0 {
|
||||
// TODO(gri): For gc-generated constants, the denominator is
|
||||
// often a large power of two. Use a more compact representation.
|
||||
p.bytes(exact.Bytes(exact.Denom(x)))
|
||||
func (p *exporter) fraction(x exact.Value) {
|
||||
sign := exact.Sign(x)
|
||||
p.int(sign)
|
||||
if sign == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.ufloat(exact.Num(x))
|
||||
p.ufloat(exact.Denom(x))
|
||||
}
|
||||
|
||||
func (p *exporter) ufloat(x exact.Value) {
|
||||
mant := exact.Bytes(x)
|
||||
exp8 := -1
|
||||
for i, b := range mant {
|
||||
if b != 0 {
|
||||
exp8 = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if exp8 < 0 {
|
||||
panic(fmt.Sprintf("%s has no mantissa", x))
|
||||
}
|
||||
p.int(exp8 * 8)
|
||||
p.bytes(mant[exp8:])
|
||||
}
|
||||
|
||||
func (p *exporter) typ(typ types.Type) {
|
||||
@ -215,20 +256,20 @@ func (p *exporter) typ(typ types.Type) {
|
||||
// This permits faithful reconstruction of the alias type (i.e.,
|
||||
// keeping the name). If we decide to eliminate the distinction
|
||||
// between the alias types, this code can go.
|
||||
p.int(_Basic)
|
||||
p.int(basicTag)
|
||||
p.string(t.Name())
|
||||
|
||||
case *types.Array:
|
||||
p.int(_Array)
|
||||
p.typ(t.Elem())
|
||||
p.int(arrayTag)
|
||||
p.int64(t.Len())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Slice:
|
||||
p.int(_Slice)
|
||||
p.int(sliceTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Struct:
|
||||
p.int(_Struct)
|
||||
p.int(structTag)
|
||||
n := t.NumFields()
|
||||
p.int(n)
|
||||
for i := 0; i < n; i++ {
|
||||
@ -237,35 +278,44 @@ func (p *exporter) typ(typ types.Type) {
|
||||
}
|
||||
|
||||
case *types.Pointer:
|
||||
p.int(_Pointer)
|
||||
p.int(pointerTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
p.int(_Signature)
|
||||
p.int(signatureTag)
|
||||
p.signature(t)
|
||||
|
||||
case *types.Interface:
|
||||
p.int(_Interface)
|
||||
n := t.NumMethods()
|
||||
p.int(interfaceTag)
|
||||
|
||||
// write methods
|
||||
n := t.NumExplicitMethods()
|
||||
p.int(n)
|
||||
for i := 0; i < n; i++ {
|
||||
m := t.Method(i)
|
||||
m := t.ExplicitMethod(i)
|
||||
p.qualifiedName(m.Pkg(), m.Name())
|
||||
p.signature(m.Type().(*types.Signature))
|
||||
p.typ(m.Type())
|
||||
}
|
||||
|
||||
// write embedded interfaces
|
||||
m := t.NumEmbeddeds()
|
||||
p.int(m)
|
||||
for i := 0; i < m; i++ {
|
||||
p.typ(t.Embedded(i))
|
||||
}
|
||||
|
||||
case *types.Map:
|
||||
p.int(_Map)
|
||||
p.int(mapTag)
|
||||
p.typ(t.Key())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Chan:
|
||||
p.int(_Chan)
|
||||
p.int(chanTag)
|
||||
p.int(int(t.Dir()))
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Named:
|
||||
p.int(_Named)
|
||||
p.int(namedTag)
|
||||
|
||||
// write type object
|
||||
obj := t.Obj()
|
||||
@ -281,7 +331,7 @@ func (p *exporter) typ(typ types.Type) {
|
||||
for i := 0; i < n; i++ {
|
||||
m := t.Method(i)
|
||||
p.string(m.Name())
|
||||
p.signature(m.Type().(*types.Signature))
|
||||
p.typ(m.Type())
|
||||
}
|
||||
|
||||
default:
|
||||
@ -302,7 +352,7 @@ func (p *exporter) field(f *types.Var) {
|
||||
|
||||
func (p *exporter) qualifiedName(pkg *types.Package, name string) {
|
||||
p.string(name)
|
||||
// exported names don't write package
|
||||
// exported names don't need package
|
||||
if !exported(name) {
|
||||
if pkg == nil {
|
||||
panic(fmt.Sprintf("nil package for unexported qualified name %s", name))
|
||||
|
@ -42,7 +42,7 @@ func ImportData(imports map[string]*types.Package, data []byte) (*types.Package,
|
||||
}
|
||||
p.typList = append(p.typList, types.Universe.Lookup("error").Type())
|
||||
|
||||
if v := p.int(); v != version {
|
||||
if v := p.string(); v != version {
|
||||
return nil, fmt.Errorf("unknown version: got %d; want %d", v, version)
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ func (p *importer) pkg() *types.Package {
|
||||
}
|
||||
|
||||
// otherwise, i is the package tag (< 0)
|
||||
if i != _Package {
|
||||
if i != packageTag {
|
||||
panic(fmt.Sprintf("unexpected package tag %d", i))
|
||||
}
|
||||
|
||||
@ -107,16 +107,16 @@ func (p *importer) pkg() *types.Package {
|
||||
func (p *importer) obj(pkg *types.Package) {
|
||||
var obj types.Object
|
||||
switch tag := p.int(); tag {
|
||||
case _Const:
|
||||
obj = types.NewConst(token.NoPos, pkg, p.string(), p.typ(), p.val())
|
||||
case _Type:
|
||||
case constTag:
|
||||
obj = types.NewConst(token.NoPos, pkg, p.string(), p.typ(), p.value())
|
||||
case typeTag:
|
||||
// type object is added to scope via respective named type
|
||||
_ = p.typ().(*types.Named)
|
||||
return
|
||||
case _Var:
|
||||
case varTag:
|
||||
obj = types.NewVar(token.NoPos, pkg, p.string(), p.typ())
|
||||
case _Func:
|
||||
obj = types.NewFunc(token.NoPos, pkg, p.string(), p.signature())
|
||||
case funcTag:
|
||||
obj = types.NewFunc(token.NoPos, pkg, p.string(), p.typ().(*types.Signature))
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected object tag %d", tag))
|
||||
}
|
||||
@ -126,43 +126,64 @@ func (p *importer) obj(pkg *types.Package) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *importer) val() exact.Value {
|
||||
func (p *importer) value() exact.Value {
|
||||
switch kind := exact.Kind(p.int()); kind {
|
||||
case exact.Bool:
|
||||
return exact.MakeBool(p.bool())
|
||||
case exact.String:
|
||||
case falseTag:
|
||||
return exact.MakeBool(false)
|
||||
case trueTag:
|
||||
return exact.MakeBool(true)
|
||||
case stringTag:
|
||||
return exact.MakeString(p.string())
|
||||
case exact.Int:
|
||||
return p.intVal()
|
||||
case exact.Float:
|
||||
return p.floatVal()
|
||||
case exact.Complex:
|
||||
re := p.floatVal()
|
||||
im := p.floatVal()
|
||||
case intTag:
|
||||
return exact.MakeInt64(p.int64())
|
||||
case floatTag:
|
||||
return p.float()
|
||||
case fractionTag:
|
||||
return p.fraction()
|
||||
case complexTag:
|
||||
re := p.fraction()
|
||||
im := p.fraction()
|
||||
return exact.BinaryOp(re, token.ADD, exact.MakeImag(im))
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected value kind %d", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *importer) intVal() exact.Value {
|
||||
func (p *importer) float() exact.Value {
|
||||
sign := p.int()
|
||||
var bytes []byte
|
||||
if sign != 0 {
|
||||
bytes = p.bytes()
|
||||
if sign == 0 {
|
||||
return exact.MakeInt64(0)
|
||||
}
|
||||
x := exact.MakeFromBytes(bytes)
|
||||
|
||||
x := p.ufloat()
|
||||
if sign < 0 {
|
||||
x = exact.UnaryOp(token.SUB, x, 0)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (p *importer) floatVal() exact.Value {
|
||||
x := p.intVal()
|
||||
if exact.Sign(x) != 0 {
|
||||
y := exact.MakeFromBytes(p.bytes())
|
||||
x = exact.BinaryOp(x, token.QUO, y)
|
||||
func (p *importer) fraction() exact.Value {
|
||||
sign := p.int()
|
||||
if sign == 0 {
|
||||
return exact.MakeInt64(0)
|
||||
}
|
||||
|
||||
x := exact.BinaryOp(p.ufloat(), token.QUO, p.ufloat())
|
||||
if sign < 0 {
|
||||
x = exact.UnaryOp(token.SUB, x, 0)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (p *importer) ufloat() exact.Value {
|
||||
exp := p.int()
|
||||
x := exact.MakeFromBytes(p.bytes())
|
||||
switch {
|
||||
case exp < 0:
|
||||
d := exact.Shift(exact.MakeInt64(1), token.SHL, uint(-exp))
|
||||
x = exact.BinaryOp(x, token.QUO, d)
|
||||
case exp > 0:
|
||||
x = exact.Shift(x, token.SHL, uint(exp))
|
||||
}
|
||||
return x
|
||||
}
|
||||
@ -180,26 +201,27 @@ func (p *importer) typ() types.Type {
|
||||
|
||||
// otherwise, i is the type tag (< 0)
|
||||
switch i {
|
||||
case _Basic:
|
||||
case basicTag:
|
||||
t := types.Universe.Lookup(p.string()).(*types.TypeName).Type().(*types.Basic)
|
||||
p.record(t)
|
||||
return t
|
||||
|
||||
case _Array:
|
||||
case arrayTag:
|
||||
t := new(types.Array)
|
||||
p.record(t)
|
||||
|
||||
*t = *types.NewArray(p.typ(), p.int64())
|
||||
n := p.int64()
|
||||
*t = *types.NewArray(p.typ(), n)
|
||||
return t
|
||||
|
||||
case _Slice:
|
||||
case sliceTag:
|
||||
t := new(types.Slice)
|
||||
p.record(t)
|
||||
|
||||
*t = *types.NewSlice(p.typ())
|
||||
return t
|
||||
|
||||
case _Struct:
|
||||
case structTag:
|
||||
t := new(types.Struct)
|
||||
p.record(t)
|
||||
|
||||
@ -213,47 +235,52 @@ func (p *importer) typ() types.Type {
|
||||
*t = *types.NewStruct(fields, tags)
|
||||
return t
|
||||
|
||||
case _Pointer:
|
||||
case pointerTag:
|
||||
t := new(types.Pointer)
|
||||
p.record(t)
|
||||
|
||||
*t = *types.NewPointer(p.typ())
|
||||
return t
|
||||
|
||||
case _Signature:
|
||||
case signatureTag:
|
||||
t := new(types.Signature)
|
||||
p.record(t)
|
||||
|
||||
*t = *p.signature()
|
||||
return t
|
||||
|
||||
case _Interface:
|
||||
case interfaceTag:
|
||||
t := new(types.Interface)
|
||||
p.record(t)
|
||||
|
||||
methods := make([]*types.Func, p.int())
|
||||
for i := range methods {
|
||||
pkg, name := p.qualifiedName()
|
||||
methods[i] = types.NewFunc(token.NoPos, pkg, name, p.signature())
|
||||
methods[i] = types.NewFunc(token.NoPos, pkg, name, p.typ().(*types.Signature))
|
||||
}
|
||||
*t = *types.NewInterface(methods, nil)
|
||||
|
||||
embeddeds := make([]*types.Named, p.int())
|
||||
for i := range embeddeds {
|
||||
embeddeds[i] = p.typ().(*types.Named)
|
||||
}
|
||||
*t = *types.NewInterface(methods, embeddeds)
|
||||
return t
|
||||
|
||||
case _Map:
|
||||
case mapTag:
|
||||
t := new(types.Map)
|
||||
p.record(t)
|
||||
|
||||
*t = *types.NewMap(p.typ(), p.typ())
|
||||
return t
|
||||
|
||||
case _Chan:
|
||||
case chanTag:
|
||||
t := new(types.Chan)
|
||||
p.record(t)
|
||||
|
||||
*t = *types.NewChan(types.ChanDir(p.int()), p.typ())
|
||||
return t
|
||||
|
||||
case _Named:
|
||||
case namedTag:
|
||||
// import type object
|
||||
name := p.string()
|
||||
pkg := p.pkg()
|
||||
@ -277,7 +304,7 @@ func (p *importer) typ() types.Type {
|
||||
// read associated methods
|
||||
n := p.int()
|
||||
for i := 0; i < n; i++ {
|
||||
t.AddMethod(types.NewFunc(token.NoPos, pkg, p.string(), p.signature()))
|
||||
t.AddMethod(types.NewFunc(token.NoPos, pkg, p.string(), p.typ().(*types.Signature)))
|
||||
}
|
||||
|
||||
return t
|
||||
|
@ -5,19 +5,27 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "code.google.com/p/go.tools/go/gcimporter"
|
||||
"code.google.com/p/go.tools/go/gcimporter"
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
||||
var verbose = flag.Bool("importer.v", false, "verbose mode")
|
||||
|
||||
var tests = []string{
|
||||
`package p`,
|
||||
|
||||
@ -84,34 +92,60 @@ func TestImportSrc(t *testing.T) {
|
||||
t.Errorf("typecheck failed: %s", err)
|
||||
continue
|
||||
}
|
||||
testExportImport(t, pkg)
|
||||
testExportImport(t, pkg, "")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gri) expand to std library
|
||||
var libs = []string{
|
||||
"../exact",
|
||||
"../gcimporter",
|
||||
"../importer",
|
||||
"../types",
|
||||
"../types/typemap",
|
||||
}
|
||||
func TestImportStdLib(t *testing.T) {
|
||||
start := time.Now()
|
||||
|
||||
func TestImportLib(t *testing.T) {
|
||||
// TODO(gri) Enable once we can run these tests on builders.
|
||||
return
|
||||
for _, lib := range libs {
|
||||
pkg, err := pkgFor(lib)
|
||||
libs, err := stdLibs()
|
||||
if err != nil {
|
||||
t.Fatalf("could not compute list of std libraries: %s", err)
|
||||
}
|
||||
|
||||
// make sure printed go/types types and gc-imported types
|
||||
// can be compared reasonably well
|
||||
types.GcCompatibilityMode = true
|
||||
|
||||
var totSize, totGcsize, n int
|
||||
for _, lib := range libs {
|
||||
// limit run time for short tests
|
||||
if testing.Short() && time.Since(start) >= 750*time.Millisecond {
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err := pkgForPath(lib)
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
// ok
|
||||
case *build.NoGoError:
|
||||
// no Go files - ignore
|
||||
continue
|
||||
default:
|
||||
t.Errorf("typecheck failed: %s", err)
|
||||
continue
|
||||
}
|
||||
testExportImport(t, pkg)
|
||||
|
||||
size, gcsize := testExportImport(t, pkg, lib)
|
||||
if *verbose {
|
||||
fmt.Printf("%s\t%d\t%d\t%d%%\n", lib, size, gcsize, int(float64(size)*100/float64(gcsize)))
|
||||
}
|
||||
totSize += size
|
||||
totGcsize += gcsize
|
||||
n++
|
||||
}
|
||||
|
||||
if *verbose {
|
||||
fmt.Printf("\n%d\t%d\t%d%%\n", totSize, totGcsize, int(float64(totSize)*100/float64(totGcsize)))
|
||||
}
|
||||
|
||||
types.GcCompatibilityMode = false
|
||||
}
|
||||
|
||||
func testExportImport(t *testing.T, pkg0 *types.Package) {
|
||||
func testExportImport(t *testing.T, pkg0 *types.Package, path string) (size, gcsize int) {
|
||||
data := ExportData(pkg0)
|
||||
size = len(data)
|
||||
|
||||
imports := make(map[string]*types.Package)
|
||||
pkg1, err := ImportData(imports, data)
|
||||
@ -123,8 +157,30 @@ func testExportImport(t *testing.T, pkg0 *types.Package) {
|
||||
s0 := pkgString(pkg0)
|
||||
s1 := pkgString(pkg1)
|
||||
if s1 != s0 {
|
||||
t.Errorf("package %s: \ngot:\n%s\nwant:\n%s\n", pkg0.Name(), s1, s0)
|
||||
t.Errorf("package %s: \nimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s1, s0)
|
||||
}
|
||||
|
||||
// If we have a standard library, compare also against the gcimported package.
|
||||
if path == "" {
|
||||
return // not std library
|
||||
}
|
||||
|
||||
gcdata, err := gcExportData(path)
|
||||
gcsize = len(gcdata)
|
||||
|
||||
imports = make(map[string]*types.Package)
|
||||
pkg2, err := gcImportData(imports, gcdata, path)
|
||||
if err != nil {
|
||||
t.Errorf("package %s: gcimport failed: %s", pkg0.Name(), err)
|
||||
return
|
||||
}
|
||||
|
||||
s2 := pkgString(pkg2)
|
||||
if s2 != s0 {
|
||||
t.Errorf("package %s: \ngcimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s2, s0)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func pkgForSource(src string) (*types.Package, error) {
|
||||
@ -139,11 +195,11 @@ func pkgForSource(src string) (*types.Package, error) {
|
||||
return types.Check("import-test", fset, []*ast.File{f})
|
||||
}
|
||||
|
||||
func pkgFor(path string) (*types.Package, error) {
|
||||
func pkgForPath(path string) (*types.Package, error) {
|
||||
// collect filenames
|
||||
ctxt := build.Default
|
||||
pkginfo, err := ctxt.ImportDir(path, 0)
|
||||
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
|
||||
pkginfo, err := ctxt.Import(path, "", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
|
||||
@ -153,16 +209,19 @@ func pkgFor(path string) (*types.Package, error) {
|
||||
files := make([]*ast.File, len(filenames))
|
||||
for i, filename := range filenames {
|
||||
var err error
|
||||
files[i], err = parser.ParseFile(fset, filepath.Join(path, filename), nil, 0)
|
||||
files[i], err = parser.ParseFile(fset, filepath.Join(pkginfo.Dir, filename), nil, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// typecheck files
|
||||
return types.Check(path, fset, files)
|
||||
// (we only care about exports and thus can ignore function bodies)
|
||||
conf := types.Config{IgnoreFuncBodies: true, FakeImportC: true}
|
||||
return conf.Check(path, fset, files, nil)
|
||||
}
|
||||
|
||||
// pkgString returns a string representation of a package's exported interface.
|
||||
func pkgString(pkg *types.Package) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
@ -176,16 +235,35 @@ func pkgString(pkg *types.Package) string {
|
||||
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
// For now only print constant values if they are not float
|
||||
// or complex. This permits comparing go/types results with
|
||||
// gc-generated gcimported package interfaces.
|
||||
info := obj.Type().Underlying().(*types.Basic).Info()
|
||||
if info&types.IsFloat == 0 && info&types.IsComplex == 0 {
|
||||
fmt.Fprintf(&buf, " = %s", obj.Val())
|
||||
}
|
||||
|
||||
case *types.TypeName:
|
||||
// Print associated methods.
|
||||
// Basic types (e.g., unsafe.Pointer) have *types.Basic
|
||||
// type rather than *types.Named; so we need to check.
|
||||
if typ, _ := obj.Type().(*types.Named); typ != nil {
|
||||
if n := typ.NumMethods(); n > 0 {
|
||||
buf.WriteString("\nmethods (\n")
|
||||
// Sort methods by name so that we get the
|
||||
// same order independent of whether the
|
||||
// methods got imported or coming directly
|
||||
// for the source.
|
||||
// TODO(gri) This should probably be done
|
||||
// in go/types.
|
||||
list := make([]*types.Func, n)
|
||||
for i := 0; i < n; i++ {
|
||||
fmt.Fprintf(&buf, "\t%s\n", typ.Method(i))
|
||||
list[i] = typ.Method(i)
|
||||
}
|
||||
sort.Sort(byName(list))
|
||||
|
||||
buf.WriteString("\nmethods (\n")
|
||||
for _, m := range list {
|
||||
fmt.Fprintf(&buf, "\t%s\n", m)
|
||||
}
|
||||
buf.WriteString(")")
|
||||
}
|
||||
@ -197,3 +275,78 @@ func pkgString(pkg *types.Package) string {
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var stdLibRoot = filepath.Join(runtime.GOROOT(), "src", "pkg") + string(filepath.Separator)
|
||||
|
||||
// The following std libraries are excluded from the stdLibs list.
|
||||
var excluded = map[string]bool{
|
||||
"builtin": true, // contains type declarations with cycles
|
||||
"unsafe": true, // contains fake declarations
|
||||
}
|
||||
|
||||
// stdLibs returns the list if standard library package paths.
|
||||
func stdLibs() (list []string, err error) {
|
||||
err = filepath.Walk(stdLibRoot, func(path string, info os.FileInfo, err error) error {
|
||||
if err == nil && info.IsDir() {
|
||||
if info.Name() == "testdata" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
pkgPath := path[len(stdLibRoot):] // remove stdLibRoot
|
||||
if len(pkgPath) > 0 && !excluded[pkgPath] {
|
||||
list = append(list, pkgPath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type byName []*types.Func
|
||||
|
||||
func (a byName) Len() int { return len(a) }
|
||||
func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byName) Less(i, j int) bool { return a[i].Name() < a[j].Name() }
|
||||
|
||||
// gcExportData returns the gc-generated export data for the given path.
|
||||
// It is based on a trimmed-down version of gcimporter.Import which does
|
||||
// not do the actual import, does not handle package unsafe, and assumes
|
||||
// that path is a correct standard library package path (no canonicalization,
|
||||
// or handling of local import paths).
|
||||
func gcExportData(path string) ([]byte, error) {
|
||||
filename, id := gcimporter.FindPkg(path, "")
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("can't find import: %s", path)
|
||||
}
|
||||
if id != path {
|
||||
panic("path should be canonicalized")
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buf := bufio.NewReader(f)
|
||||
if err = gcimporter.FindExportData(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []byte
|
||||
for {
|
||||
line, err := buf.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = append(data, line...)
|
||||
// export data ends in "$$\n"
|
||||
if len(line) == 3 && line[0] == '$' && line[1] == '$' {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gcImportData(imports map[string]*types.Package, data []byte, path string) (*types.Package, error) {
|
||||
filename := fmt.Sprintf("<filename for %s>", path) // so we have a decent error message if necessary
|
||||
return gcimporter.ImportData(imports, filename, path, bufio.NewReader(bytes.NewBuffer(data)))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user