From 30645c4bc6178174e0d6dc57cab2ced184c8d0aa Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 20 Dec 2013 16:43:31 -0800 Subject: [PATCH] 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 --- go/importer/export.go | 168 +++++++++++++++++++----------- go/importer/import.go | 113 ++++++++++++-------- go/importer/import_test.go | 205 ++++++++++++++++++++++++++++++++----- 3 files changed, 358 insertions(+), 128 deletions(-) diff --git a/go/importer/export.go b/go/importer/export.go index c4d634fc654..62f51535558 100644 --- a/go/importer/export.go +++ b/go/importer/export.go @@ -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)) diff --git a/go/importer/import.go b/go/importer/import.go index c5e7850d50c..0832050ea24 100644 --- a/go/importer/import.go +++ b/go/importer/import.go @@ -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 diff --git a/go/importer/import_test.go b/go/importer/import_test.go index 534feaf0f97..b47b22f3025 100644 --- a/go/importer/import_test.go +++ b/go/importer/import_test.go @@ -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 + 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 { - pkg, err := pkgFor(lib) - if err != nil { + // 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: - fmt.Fprintf(&buf, " = %s", obj.Val()) + // 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("", path) // so we have a decent error message if necessary + return gcimporter.ImportData(imports, filename, path, bufio.NewReader(bytes.NewBuffer(data))) +}