mirror of https://github.com/golang/go.git
go/internal/gcimporter: remove support for binary export data
There should no longer be any clients of the binary export data format, and it has been deleted from the standard library, so we can now delete it from x/tools. This change deletes bimport.go, bexport.go, and bexport_test.go, transferring any shared code to the corresponding "i" files. Fixes golang/go#37312 Change-Id: I25d8a36e89c33e10de68b586245722794c4d52c3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/229987 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
352a5409fa
commit
655248709e
|
|
@ -85,15 +85,11 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package,
|
|||
return gcimporter.ImportData(imports, path, path, bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// The indexed export format starts with an 'i'; the older
|
||||
// binary export format starts with a 'c', 'd', or 'v'
|
||||
// (from "version"). Select appropriate importer.
|
||||
if len(data) > 0 && data[0] == 'i' {
|
||||
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
// The indexed export format starts with an 'i'.
|
||||
if len(data) == 0 || data[0] != 'i' {
|
||||
return nil, fmt.Errorf("unknown export data format")
|
||||
}
|
||||
|
||||
_, pkg, err := gcimporter.BImportData(fset, imports, data, path)
|
||||
_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,852 +0,0 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Binary package export.
|
||||
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go;
|
||||
// see that file for specification of the format.
|
||||
|
||||
package gcimporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// If debugFormat is set, each integer and string value is preceded by a marker
|
||||
// and position information in the encoding. This mechanism permits an importer
|
||||
// to recognize immediately when it is out of sync. The importer recognizes this
|
||||
// mode automatically (i.e., it can import export data produced with debugging
|
||||
// support even if debugFormat is not set at the time of import). This mode will
|
||||
// lead to massively larger export data (by a factor of 2 to 3) and should only
|
||||
// be enabled during development and debugging.
|
||||
//
|
||||
// NOTE: This flag is the first flag to enable if importing dies because of
|
||||
// (suspected) format errors, and whenever a change is made to the format.
|
||||
const debugFormat = false // default: false
|
||||
|
||||
// If trace is set, debugging output is printed to std out.
|
||||
const trace = false // default: false
|
||||
|
||||
// Current export format version. Increase with each format change.
|
||||
// Note: The latest binary (non-indexed) export format is at version 6.
|
||||
// This exporter is still at level 4, but it doesn't matter since
|
||||
// the binary importer can handle older versions just fine.
|
||||
// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE
|
||||
// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE
|
||||
// 4: type name objects support type aliases, uses aliasTag
|
||||
// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used)
|
||||
// 2: removed unused bool in ODCL export (compiler only)
|
||||
// 1: header format change (more regular), export package for _ struct fields
|
||||
// 0: Go1.7 encoding
|
||||
const exportVersion = 4
|
||||
|
||||
// trackAllTypes enables cycle tracking for all types, not just named
|
||||
// types. The existing compiler invariants assume that unnamed types
|
||||
// that are not completely set up are not used, or else there are spurious
|
||||
// errors.
|
||||
// If disabled, only named types are tracked, possibly leading to slightly
|
||||
// less efficient encoding in rare cases. It also prevents the export of
|
||||
// some corner-case type declarations (but those are not handled correctly
|
||||
// with with the textual export format either).
|
||||
// TODO(gri) enable and remove once issues caused by it are fixed
|
||||
const trackAllTypes = false
|
||||
|
||||
type exporter struct {
|
||||
fset *token.FileSet
|
||||
out bytes.Buffer
|
||||
|
||||
// object -> index maps, indexed in order of serialization
|
||||
strIndex map[string]int
|
||||
pkgIndex map[*types.Package]int
|
||||
typIndex map[types.Type]int
|
||||
|
||||
// position encoding
|
||||
posInfoFormat bool
|
||||
prevFile string
|
||||
prevLine int
|
||||
|
||||
// debugging support
|
||||
written int // bytes written
|
||||
indent int // for trace
|
||||
}
|
||||
|
||||
// internalError represents an error generated inside this package.
|
||||
type internalError string
|
||||
|
||||
func (e internalError) Error() string { return "gcimporter: " + string(e) }
|
||||
|
||||
func internalErrorf(format string, args ...interface{}) error {
|
||||
return internalError(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// BExportData returns binary export data for pkg.
|
||||
// If no file set is provided, position info will be missing.
|
||||
func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if ierr, ok := e.(internalError); ok {
|
||||
err = ierr
|
||||
return
|
||||
}
|
||||
// Not an internal error; panic again.
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
|
||||
p := exporter{
|
||||
fset: fset,
|
||||
strIndex: map[string]int{"": 0}, // empty string is mapped to 0
|
||||
pkgIndex: make(map[*types.Package]int),
|
||||
typIndex: make(map[types.Type]int),
|
||||
posInfoFormat: true, // TODO(gri) might become a flag, eventually
|
||||
}
|
||||
|
||||
// write version info
|
||||
// The version string must start with "version %d" where %d is the version
|
||||
// number. Additional debugging information may follow after a blank; that
|
||||
// text is ignored by the importer.
|
||||
p.rawStringln(fmt.Sprintf("version %d", exportVersion))
|
||||
var debug string
|
||||
if debugFormat {
|
||||
debug = "debug"
|
||||
}
|
||||
p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly
|
||||
p.bool(trackAllTypes)
|
||||
p.bool(p.posInfoFormat)
|
||||
|
||||
// --- generic export data ---
|
||||
|
||||
// populate type map with predeclared "known" types
|
||||
for index, typ := range predeclared() {
|
||||
p.typIndex[typ] = index
|
||||
}
|
||||
if len(p.typIndex) != len(predeclared()) {
|
||||
return nil, internalError("duplicate entries in type map?")
|
||||
}
|
||||
|
||||
// write package data
|
||||
p.pkg(pkg, true)
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// write objects
|
||||
objcount := 0
|
||||
scope := pkg.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
if !ast.IsExported(name) {
|
||||
continue
|
||||
}
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.obj(scope.Lookup(name))
|
||||
objcount++
|
||||
}
|
||||
|
||||
// indicate end of list
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.tag(endTag)
|
||||
|
||||
// for self-verification only (redundant)
|
||||
p.int(objcount)
|
||||
|
||||
if trace {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
// --- end of export data ---
|
||||
|
||||
return p.out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *exporter) pkg(pkg *types.Package, emptypath bool) {
|
||||
if pkg == nil {
|
||||
panic(internalError("unexpected nil pkg"))
|
||||
}
|
||||
|
||||
// if we saw the package before, write its index (>= 0)
|
||||
if i, ok := p.pkgIndex[pkg]; ok {
|
||||
p.index('P', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the package, write the package tag (< 0) and package data
|
||||
if trace {
|
||||
p.tracef("P%d = { ", len(p.pkgIndex))
|
||||
defer p.tracef("} ")
|
||||
}
|
||||
p.pkgIndex[pkg] = len(p.pkgIndex)
|
||||
|
||||
p.tag(packageTag)
|
||||
p.string(pkg.Name())
|
||||
if emptypath {
|
||||
p.string("")
|
||||
} else {
|
||||
p.string(pkg.Path())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) obj(obj types.Object) {
|
||||
switch obj := obj.(type) {
|
||||
case *types.Const:
|
||||
p.tag(constTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
p.value(obj.Val())
|
||||
|
||||
case *types.TypeName:
|
||||
if obj.IsAlias() {
|
||||
p.tag(aliasTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
} else {
|
||||
p.tag(typeTag)
|
||||
}
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Var:
|
||||
p.tag(varTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
p.typ(obj.Type())
|
||||
|
||||
case *types.Func:
|
||||
p.tag(funcTag)
|
||||
p.pos(obj)
|
||||
p.qualifiedName(obj)
|
||||
sig := obj.Type().(*types.Signature)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected object %v (%T)", obj, obj))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) pos(obj types.Object) {
|
||||
if !p.posInfoFormat {
|
||||
return
|
||||
}
|
||||
|
||||
file, line := p.fileLine(obj)
|
||||
if file == p.prevFile {
|
||||
// common case: write line delta
|
||||
// delta == 0 means different file or no line change
|
||||
delta := line - p.prevLine
|
||||
p.int(delta)
|
||||
if delta == 0 {
|
||||
p.int(-1) // -1 means no file change
|
||||
}
|
||||
} else {
|
||||
// different file
|
||||
p.int(0)
|
||||
// Encode filename as length of common prefix with previous
|
||||
// filename, followed by (possibly empty) suffix. Filenames
|
||||
// frequently share path prefixes, so this can save a lot
|
||||
// of space and make export data size less dependent on file
|
||||
// path length. The suffix is unlikely to be empty because
|
||||
// file names tend to end in ".go".
|
||||
n := commonPrefixLen(p.prevFile, file)
|
||||
p.int(n) // n >= 0
|
||||
p.string(file[n:]) // write suffix only
|
||||
p.prevFile = file
|
||||
p.int(line)
|
||||
}
|
||||
p.prevLine = line
|
||||
}
|
||||
|
||||
func (p *exporter) fileLine(obj types.Object) (file string, line int) {
|
||||
if p.fset != nil {
|
||||
pos := p.fset.Position(obj.Pos())
|
||||
file = pos.Filename
|
||||
line = pos.Line
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func commonPrefixLen(a, b string) int {
|
||||
if len(a) > len(b) {
|
||||
a, b = b, a
|
||||
}
|
||||
// len(a) <= len(b)
|
||||
i := 0
|
||||
for i < len(a) && a[i] == b[i] {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *exporter) qualifiedName(obj types.Object) {
|
||||
p.string(obj.Name())
|
||||
p.pkg(obj.Pkg(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) typ(t types.Type) {
|
||||
if t == nil {
|
||||
panic(internalError("nil type"))
|
||||
}
|
||||
|
||||
// Possible optimization: Anonymous pointer types *T where
|
||||
// T is a named type are common. We could canonicalize all
|
||||
// such types *T to a single type PT = *T. This would lead
|
||||
// to at most one *T entry in typIndex, and all future *T's
|
||||
// would be encoded as the respective index directly. Would
|
||||
// save 1 byte (pointerTag) per *T and reduce the typIndex
|
||||
// size (at the cost of a canonicalization map). We can do
|
||||
// this later, without encoding format change.
|
||||
|
||||
// if we saw the type before, write its index (>= 0)
|
||||
if i, ok := p.typIndex[t]; ok {
|
||||
p.index('T', i)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, remember the type, write the type tag (< 0) and type data
|
||||
if trackAllTypes {
|
||||
if trace {
|
||||
p.tracef("T%d = {>\n", len(p.typIndex))
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
if !trackAllTypes {
|
||||
// if we don't track all types, track named types now
|
||||
p.typIndex[t] = len(p.typIndex)
|
||||
}
|
||||
|
||||
p.tag(namedTag)
|
||||
p.pos(t.Obj())
|
||||
p.qualifiedName(t.Obj())
|
||||
p.typ(t.Underlying())
|
||||
if !types.IsInterface(t) {
|
||||
p.assocMethods(t)
|
||||
}
|
||||
|
||||
case *types.Array:
|
||||
p.tag(arrayTag)
|
||||
p.int64(t.Len())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Slice:
|
||||
p.tag(sliceTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *dddSlice:
|
||||
p.tag(dddTag)
|
||||
p.typ(t.elem)
|
||||
|
||||
case *types.Struct:
|
||||
p.tag(structTag)
|
||||
p.fieldList(t)
|
||||
|
||||
case *types.Pointer:
|
||||
p.tag(pointerTag)
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
p.tag(signatureTag)
|
||||
p.paramList(t.Params(), t.Variadic())
|
||||
p.paramList(t.Results(), false)
|
||||
|
||||
case *types.Interface:
|
||||
p.tag(interfaceTag)
|
||||
p.iface(t)
|
||||
|
||||
case *types.Map:
|
||||
p.tag(mapTag)
|
||||
p.typ(t.Key())
|
||||
p.typ(t.Elem())
|
||||
|
||||
case *types.Chan:
|
||||
p.tag(chanTag)
|
||||
p.int(int(3 - t.Dir())) // hack
|
||||
p.typ(t.Elem())
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected type %T: %s", t, t))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) assocMethods(named *types.Named) {
|
||||
// Sort methods (for determinism).
|
||||
var methods []*types.Func
|
||||
for i := 0; i < named.NumMethods(); i++ {
|
||||
methods = append(methods, named.Method(i))
|
||||
}
|
||||
sort.Sort(methodsByName(methods))
|
||||
|
||||
p.int(len(methods))
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("associated methods {>\n")
|
||||
}
|
||||
|
||||
for i, m := range methods {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
name := m.Name()
|
||||
p.string(name)
|
||||
if !exported(name) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
sig := m.Type().(*types.Signature)
|
||||
p.paramList(types.NewTuple(sig.Recv()), false)
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
p.int(0) // dummy value for go:nointerface pragma - ignored by importer
|
||||
}
|
||||
|
||||
if trace && methods != nil {
|
||||
p.tracef("<\n} ")
|
||||
}
|
||||
}
|
||||
|
||||
type methodsByName []*types.Func
|
||||
|
||||
func (x methodsByName) Len() int { return len(x) }
|
||||
func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
|
||||
|
||||
func (p *exporter) fieldList(t *types.Struct) {
|
||||
if trace && t.NumFields() > 0 {
|
||||
p.tracef("fields {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
|
||||
p.int(t.NumFields())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.field(t.Field(i))
|
||||
p.string(t.Tag(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) field(f *types.Var) {
|
||||
if !f.IsField() {
|
||||
panic(internalError("field expected"))
|
||||
}
|
||||
|
||||
p.pos(f)
|
||||
p.fieldName(f)
|
||||
p.typ(f.Type())
|
||||
}
|
||||
|
||||
func (p *exporter) iface(t *types.Interface) {
|
||||
// TODO(gri): enable importer to load embedded interfaces,
|
||||
// then emit Embeddeds and ExplicitMethods separately here.
|
||||
p.int(0)
|
||||
|
||||
n := t.NumMethods()
|
||||
if trace && n > 0 {
|
||||
p.tracef("methods {>\n")
|
||||
defer p.tracef("<\n} ")
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < n; i++ {
|
||||
if trace && i > 0 {
|
||||
p.tracef("\n")
|
||||
}
|
||||
p.method(t.Method(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) method(m *types.Func) {
|
||||
sig := m.Type().(*types.Signature)
|
||||
if sig.Recv() == nil {
|
||||
panic(internalError("method expected"))
|
||||
}
|
||||
|
||||
p.pos(m)
|
||||
p.string(m.Name())
|
||||
if m.Name() != "_" && !ast.IsExported(m.Name()) {
|
||||
p.pkg(m.Pkg(), false)
|
||||
}
|
||||
|
||||
// interface method; no need to encode receiver.
|
||||
p.paramList(sig.Params(), sig.Variadic())
|
||||
p.paramList(sig.Results(), false)
|
||||
}
|
||||
|
||||
func (p *exporter) fieldName(f *types.Var) {
|
||||
name := f.Name()
|
||||
|
||||
if f.Anonymous() {
|
||||
// anonymous field - we distinguish between 3 cases:
|
||||
// 1) field name matches base type name and is exported
|
||||
// 2) field name matches base type name and is not exported
|
||||
// 3) field name doesn't match base type name (alias name)
|
||||
bname := basetypeName(f.Type())
|
||||
if name == bname {
|
||||
if ast.IsExported(name) {
|
||||
name = "" // 1) we don't need to know the field name or package
|
||||
} else {
|
||||
name = "?" // 2) use unexported name "?" to force package export
|
||||
}
|
||||
} else {
|
||||
// 3) indicate alias and export name as is
|
||||
// (this requires an extra "@" but this is a rare case)
|
||||
p.string("@")
|
||||
}
|
||||
}
|
||||
|
||||
p.string(name)
|
||||
if name != "" && !ast.IsExported(name) {
|
||||
p.pkg(f.Pkg(), false)
|
||||
}
|
||||
}
|
||||
|
||||
func basetypeName(typ types.Type) string {
|
||||
switch typ := deref(typ).(type) {
|
||||
case *types.Basic:
|
||||
return typ.Name()
|
||||
case *types.Named:
|
||||
return typ.Obj().Name()
|
||||
default:
|
||||
return "" // unnamed type
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) paramList(params *types.Tuple, variadic bool) {
|
||||
// use negative length to indicate unnamed parameters
|
||||
// (look at the first parameter only since either all
|
||||
// names are present or all are absent)
|
||||
n := params.Len()
|
||||
if n > 0 && params.At(0).Name() == "" {
|
||||
n = -n
|
||||
}
|
||||
p.int(n)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
q := params.At(i)
|
||||
t := q.Type()
|
||||
if variadic && i == params.Len()-1 {
|
||||
t = &dddSlice{t.(*types.Slice).Elem()}
|
||||
}
|
||||
p.typ(t)
|
||||
if n > 0 {
|
||||
name := q.Name()
|
||||
p.string(name)
|
||||
if name != "_" {
|
||||
p.pkg(q.Pkg(), false)
|
||||
}
|
||||
}
|
||||
p.string("") // no compiler-specific info
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) value(x constant.Value) {
|
||||
if trace {
|
||||
p.tracef("= ")
|
||||
}
|
||||
|
||||
switch x.Kind() {
|
||||
case constant.Bool:
|
||||
tag := falseTag
|
||||
if constant.BoolVal(x) {
|
||||
tag = trueTag
|
||||
}
|
||||
p.tag(tag)
|
||||
|
||||
case constant.Int:
|
||||
if v, exact := constant.Int64Val(x); exact {
|
||||
// common case: x fits into an int64 - use compact encoding
|
||||
p.tag(int64Tag)
|
||||
p.int64(v)
|
||||
return
|
||||
}
|
||||
// uncommon case: large x - use float encoding
|
||||
// (powers of 2 will be encoded efficiently with exponent)
|
||||
p.tag(floatTag)
|
||||
p.float(constant.ToFloat(x))
|
||||
|
||||
case constant.Float:
|
||||
p.tag(floatTag)
|
||||
p.float(x)
|
||||
|
||||
case constant.Complex:
|
||||
p.tag(complexTag)
|
||||
p.float(constant.Real(x))
|
||||
p.float(constant.Imag(x))
|
||||
|
||||
case constant.String:
|
||||
p.tag(stringTag)
|
||||
p.string(constant.StringVal(x))
|
||||
|
||||
case constant.Unknown:
|
||||
// package contains type errors
|
||||
p.tag(unknownTag)
|
||||
|
||||
default:
|
||||
panic(internalErrorf("unexpected value %v (%T)", x, x))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exporter) float(x constant.Value) {
|
||||
if x.Kind() != constant.Float {
|
||||
panic(internalErrorf("unexpected constant %v, want float", x))
|
||||
}
|
||||
// extract sign (there is no -0)
|
||||
sign := constant.Sign(x)
|
||||
if sign == 0 {
|
||||
// x == 0
|
||||
p.int(0)
|
||||
return
|
||||
}
|
||||
// x != 0
|
||||
|
||||
var f big.Float
|
||||
if v, exact := constant.Float64Val(x); exact {
|
||||
// float64
|
||||
f.SetFloat64(v)
|
||||
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
|
||||
// TODO(gri): add big.Rat accessor to constant.Value.
|
||||
r := valueToRat(num)
|
||||
f.SetRat(r.Quo(r, valueToRat(denom)))
|
||||
} else {
|
||||
// Value too large to represent as a fraction => inaccessible.
|
||||
// TODO(gri): add big.Float accessor to constant.Value.
|
||||
f.SetFloat64(math.MaxFloat64) // FIXME
|
||||
}
|
||||
|
||||
// extract exponent such that 0.5 <= m < 1.0
|
||||
var m big.Float
|
||||
exp := f.MantExp(&m)
|
||||
|
||||
// extract mantissa as *big.Int
|
||||
// - set exponent large enough so mant satisfies mant.IsInt()
|
||||
// - get *big.Int from mant
|
||||
m.SetMantExp(&m, int(m.MinPrec()))
|
||||
mant, acc := m.Int(nil)
|
||||
if acc != big.Exact {
|
||||
panic(internalError("internal error"))
|
||||
}
|
||||
|
||||
p.int(sign)
|
||||
p.int(exp)
|
||||
p.string(string(mant.Bytes()))
|
||||
}
|
||||
|
||||
func valueToRat(x constant.Value) *big.Rat {
|
||||
// Convert little-endian to big-endian.
|
||||
// I can't believe this is necessary.
|
||||
bytes := constant.Bytes(x)
|
||||
for i := 0; i < len(bytes)/2; i++ {
|
||||
bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
|
||||
}
|
||||
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||
}
|
||||
|
||||
func (p *exporter) bool(b bool) bool {
|
||||
if trace {
|
||||
p.tracef("[")
|
||||
defer p.tracef("= %v] ", b)
|
||||
}
|
||||
|
||||
x := 0
|
||||
if b {
|
||||
x = 1
|
||||
}
|
||||
p.int(x)
|
||||
return b
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Low-level encoders
|
||||
|
||||
func (p *exporter) index(marker byte, index int) {
|
||||
if index < 0 {
|
||||
panic(internalError("invalid index < 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%c%d ", marker, index)
|
||||
}
|
||||
p.rawInt64(int64(index))
|
||||
}
|
||||
|
||||
func (p *exporter) tag(tag int) {
|
||||
if tag >= 0 {
|
||||
panic(internalError("invalid tag >= 0"))
|
||||
}
|
||||
if debugFormat {
|
||||
p.marker('t')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%s ", tagString[-tag])
|
||||
}
|
||||
p.rawInt64(int64(tag))
|
||||
}
|
||||
|
||||
func (p *exporter) int(x int) {
|
||||
p.int64(int64(x))
|
||||
}
|
||||
|
||||
func (p *exporter) int64(x int64) {
|
||||
if debugFormat {
|
||||
p.marker('i')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%d ", x)
|
||||
}
|
||||
p.rawInt64(x)
|
||||
}
|
||||
|
||||
func (p *exporter) string(s string) {
|
||||
if debugFormat {
|
||||
p.marker('s')
|
||||
}
|
||||
if trace {
|
||||
p.tracef("%q ", s)
|
||||
}
|
||||
// if we saw the string before, write its index (>= 0)
|
||||
// (the empty string is mapped to 0)
|
||||
if i, ok := p.strIndex[s]; ok {
|
||||
p.rawInt64(int64(i))
|
||||
return
|
||||
}
|
||||
// otherwise, remember string and write its negative length and bytes
|
||||
p.strIndex[s] = len(p.strIndex)
|
||||
p.rawInt64(-int64(len(s)))
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
}
|
||||
|
||||
// marker emits a marker byte and position information which makes
|
||||
// it easy for a reader to detect if it is "out of sync". Used for
|
||||
// debugFormat format only.
|
||||
func (p *exporter) marker(m byte) {
|
||||
p.rawByte(m)
|
||||
// Enable this for help tracking down the location
|
||||
// of an incorrect marker when running in debugFormat.
|
||||
if false && trace {
|
||||
p.tracef("#%d ", p.written)
|
||||
}
|
||||
p.rawInt64(int64(p.written))
|
||||
}
|
||||
|
||||
// rawInt64 should only be used by low-level encoders.
|
||||
func (p *exporter) rawInt64(x int64) {
|
||||
var tmp [binary.MaxVarintLen64]byte
|
||||
n := binary.PutVarint(tmp[:], x)
|
||||
for i := 0; i < n; i++ {
|
||||
p.rawByte(tmp[i])
|
||||
}
|
||||
}
|
||||
|
||||
// rawStringln should only be used to emit the initial version string.
|
||||
func (p *exporter) rawStringln(s string) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
p.rawByte(s[i])
|
||||
}
|
||||
p.rawByte('\n')
|
||||
}
|
||||
|
||||
// rawByte is the bottleneck interface to write to p.out.
|
||||
// rawByte escapes b as follows (any encoding does that
|
||||
// hides '$'):
|
||||
//
|
||||
// '$' => '|' 'S'
|
||||
// '|' => '|' '|'
|
||||
//
|
||||
// Necessary so other tools can find the end of the
|
||||
// export data by searching for "$$".
|
||||
// rawByte should only be used by low-level encoders.
|
||||
func (p *exporter) rawByte(b byte) {
|
||||
switch b {
|
||||
case '$':
|
||||
// write '$' as '|' 'S'
|
||||
b = 'S'
|
||||
fallthrough
|
||||
case '|':
|
||||
// write '|' as '|' '|'
|
||||
p.out.WriteByte('|')
|
||||
p.written++
|
||||
}
|
||||
p.out.WriteByte(b)
|
||||
p.written++
|
||||
}
|
||||
|
||||
// tracef is like fmt.Printf but it rewrites the format string
|
||||
// to take care of indentation.
|
||||
func (p *exporter) tracef(format string, args ...interface{}) {
|
||||
if strings.ContainsAny(format, "<>\n") {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(format); i++ {
|
||||
// no need to deal with runes
|
||||
ch := format[i]
|
||||
switch ch {
|
||||
case '>':
|
||||
p.indent++
|
||||
continue
|
||||
case '<':
|
||||
p.indent--
|
||||
continue
|
||||
}
|
||||
buf.WriteByte(ch)
|
||||
if ch == '\n' {
|
||||
for j := p.indent; j > 0; j-- {
|
||||
buf.WriteString(". ")
|
||||
}
|
||||
}
|
||||
}
|
||||
format = buf.String()
|
||||
}
|
||||
fmt.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Debugging support.
|
||||
// (tagString is only used when tracing is enabled)
|
||||
var tagString = [...]string{
|
||||
// Packages
|
||||
-packageTag: "package",
|
||||
|
||||
// Types
|
||||
-namedTag: "named type",
|
||||
-arrayTag: "array",
|
||||
-sliceTag: "slice",
|
||||
-dddTag: "ddd",
|
||||
-structTag: "struct",
|
||||
-pointerTag: "pointer",
|
||||
-signatureTag: "signature",
|
||||
-interfaceTag: "interface",
|
||||
-mapTag: "map",
|
||||
-chanTag: "chan",
|
||||
|
||||
// Values
|
||||
-falseTag: "false",
|
||||
-trueTag: "true",
|
||||
-int64Tag: "int64",
|
||||
-floatTag: "float",
|
||||
-fractionTag: "fraction",
|
||||
-complexTag: "complex",
|
||||
-stringTag: "string",
|
||||
-unknownTag: "unknown",
|
||||
|
||||
// Type aliases
|
||||
-aliasTag: "alias",
|
||||
}
|
||||
|
|
@ -1,419 +0,0 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gcimporter_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/constant"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/internal/gcimporter"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
var isRace = false
|
||||
|
||||
func TestBExportData_stdlib(t *testing.T) {
|
||||
if runtime.Compiler == "gccgo" {
|
||||
t.Skip("gccgo standard library is inaccessible")
|
||||
}
|
||||
if runtime.GOOS == "android" {
|
||||
t.Skipf("incomplete std lib on %s", runtime.GOOS)
|
||||
}
|
||||
if isRace {
|
||||
t.Skipf("stdlib tests take too long in race mode and flake on builders")
|
||||
}
|
||||
|
||||
// Load, parse and type-check the program.
|
||||
ctxt := build.Default // copy
|
||||
ctxt.GOPATH = "" // disable GOPATH
|
||||
conf := loader.Config{
|
||||
Build: &ctxt,
|
||||
AllowErrors: true,
|
||||
}
|
||||
for _, path := range buildutil.AllPackages(conf.Build) {
|
||||
conf.Import(path)
|
||||
}
|
||||
|
||||
// Create a package containing type and value errors to ensure
|
||||
// they are properly encoded/decoded.
|
||||
f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors
|
||||
const UnknownValue = "" + 0
|
||||
type UnknownType undefined
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conf.CreateFromFiles("haserrors", f)
|
||||
|
||||
prog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
|
||||
numPkgs := len(prog.AllPackages)
|
||||
if want := 248; numPkgs < want {
|
||||
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
|
||||
}
|
||||
|
||||
for pkg, info := range prog.AllPackages {
|
||||
if info.Files == nil {
|
||||
continue // empty directory
|
||||
}
|
||||
exportdata, err := gcimporter.BExportData(conf.Fset, pkg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imports := make(map[string]*types.Package)
|
||||
fset2 := token.NewFileSet()
|
||||
n, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path())
|
||||
if err != nil {
|
||||
t.Errorf("BImportData(%s): %v", pkg.Path(), err)
|
||||
continue
|
||||
}
|
||||
if n != len(exportdata) {
|
||||
t.Errorf("BImportData(%s) decoded %d bytes, want %d",
|
||||
pkg.Path(), n, len(exportdata))
|
||||
}
|
||||
|
||||
// Compare the packages' corresponding members.
|
||||
for _, name := range pkg.Scope().Names() {
|
||||
if !ast.IsExported(name) {
|
||||
continue
|
||||
}
|
||||
obj1 := pkg.Scope().Lookup(name)
|
||||
obj2 := pkg2.Scope().Lookup(name)
|
||||
if obj2 == nil {
|
||||
t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1)
|
||||
continue
|
||||
}
|
||||
|
||||
fl1 := fileLine(conf.Fset, obj1)
|
||||
fl2 := fileLine(fset2, obj2)
|
||||
if fl1 != fl2 {
|
||||
t.Errorf("%s.%s: got posn %s, want %s",
|
||||
pkg.Path(), name, fl2, fl1)
|
||||
}
|
||||
|
||||
if err := equalObj(obj1, obj2); err != nil {
|
||||
t.Errorf("%s.%s: %s\ngot: %s\nwant: %s",
|
||||
pkg.Path(), name, err, obj2, obj1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fileLine(fset *token.FileSet, obj types.Object) string {
|
||||
posn := fset.Position(obj.Pos())
|
||||
return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
|
||||
}
|
||||
|
||||
// equalObj reports how x and y differ. They are assumed to belong to
|
||||
// different universes so cannot be compared directly.
|
||||
func equalObj(x, y types.Object) error {
|
||||
if reflect.TypeOf(x) != reflect.TypeOf(y) {
|
||||
return fmt.Errorf("%T vs %T", x, y)
|
||||
}
|
||||
xt := x.Type()
|
||||
yt := y.Type()
|
||||
switch x.(type) {
|
||||
case *types.Var, *types.Func:
|
||||
// ok
|
||||
case *types.Const:
|
||||
xval := x.(*types.Const).Val()
|
||||
yval := y.(*types.Const).Val()
|
||||
// Use string comparison for floating-point values since rounding is permitted.
|
||||
if constant.Compare(xval, token.NEQ, yval) &&
|
||||
!(xval.Kind() == constant.Float && xval.String() == yval.String()) {
|
||||
return fmt.Errorf("unequal constants %s vs %s", xval, yval)
|
||||
}
|
||||
case *types.TypeName:
|
||||
xt = xt.Underlying()
|
||||
yt = yt.Underlying()
|
||||
default:
|
||||
return fmt.Errorf("unexpected %T", x)
|
||||
}
|
||||
return equalType(xt, yt)
|
||||
}
|
||||
|
||||
func equalType(x, y types.Type) error {
|
||||
if reflect.TypeOf(x) != reflect.TypeOf(y) {
|
||||
return fmt.Errorf("unequal kinds: %T vs %T", x, y)
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case *types.Interface:
|
||||
y := y.(*types.Interface)
|
||||
// TODO(gri): enable separate emission of Embedded interfaces
|
||||
// and ExplicitMethods then use this logic.
|
||||
// if x.NumEmbeddeds() != y.NumEmbeddeds() {
|
||||
// return fmt.Errorf("unequal number of embedded interfaces: %d vs %d",
|
||||
// x.NumEmbeddeds(), y.NumEmbeddeds())
|
||||
// }
|
||||
// for i := 0; i < x.NumEmbeddeds(); i++ {
|
||||
// xi := x.Embedded(i)
|
||||
// yi := y.Embedded(i)
|
||||
// if xi.String() != yi.String() {
|
||||
// return fmt.Errorf("mismatched %th embedded interface: %s vs %s",
|
||||
// i, xi, yi)
|
||||
// }
|
||||
// }
|
||||
// if x.NumExplicitMethods() != y.NumExplicitMethods() {
|
||||
// return fmt.Errorf("unequal methods: %d vs %d",
|
||||
// x.NumExplicitMethods(), y.NumExplicitMethods())
|
||||
// }
|
||||
// for i := 0; i < x.NumExplicitMethods(); i++ {
|
||||
// xm := x.ExplicitMethod(i)
|
||||
// ym := y.ExplicitMethod(i)
|
||||
// if xm.Name() != ym.Name() {
|
||||
// return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym)
|
||||
// }
|
||||
// if err := equalType(xm.Type(), ym.Type()); err != nil {
|
||||
// return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
|
||||
// }
|
||||
// }
|
||||
if x.NumMethods() != y.NumMethods() {
|
||||
return fmt.Errorf("unequal methods: %d vs %d",
|
||||
x.NumMethods(), y.NumMethods())
|
||||
}
|
||||
for i := 0; i < x.NumMethods(); i++ {
|
||||
xm := x.Method(i)
|
||||
ym := y.Method(i)
|
||||
if xm.Name() != ym.Name() {
|
||||
return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym)
|
||||
}
|
||||
if err := equalType(xm.Type(), ym.Type()); err != nil {
|
||||
return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
|
||||
}
|
||||
}
|
||||
case *types.Array:
|
||||
y := y.(*types.Array)
|
||||
if x.Len() != y.Len() {
|
||||
return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len())
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("array elements: %s", err)
|
||||
}
|
||||
case *types.Basic:
|
||||
y := y.(*types.Basic)
|
||||
if x.Kind() != y.Kind() {
|
||||
return fmt.Errorf("unequal basic types: %s vs %s", x, y)
|
||||
}
|
||||
case *types.Chan:
|
||||
y := y.(*types.Chan)
|
||||
if x.Dir() != y.Dir() {
|
||||
return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir())
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("channel elements: %s", err)
|
||||
}
|
||||
case *types.Map:
|
||||
y := y.(*types.Map)
|
||||
if err := equalType(x.Key(), y.Key()); err != nil {
|
||||
return fmt.Errorf("map keys: %s", err)
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("map values: %s", err)
|
||||
}
|
||||
case *types.Named:
|
||||
y := y.(*types.Named)
|
||||
if x.String() != y.String() {
|
||||
return fmt.Errorf("unequal named types: %s vs %s", x, y)
|
||||
}
|
||||
case *types.Pointer:
|
||||
y := y.(*types.Pointer)
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("pointer elements: %s", err)
|
||||
}
|
||||
case *types.Signature:
|
||||
y := y.(*types.Signature)
|
||||
if err := equalType(x.Params(), y.Params()); err != nil {
|
||||
return fmt.Errorf("parameters: %s", err)
|
||||
}
|
||||
if err := equalType(x.Results(), y.Results()); err != nil {
|
||||
return fmt.Errorf("results: %s", err)
|
||||
}
|
||||
if x.Variadic() != y.Variadic() {
|
||||
return fmt.Errorf("unequal variadicity: %t vs %t",
|
||||
x.Variadic(), y.Variadic())
|
||||
}
|
||||
if (x.Recv() != nil) != (y.Recv() != nil) {
|
||||
return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv())
|
||||
}
|
||||
if x.Recv() != nil {
|
||||
// TODO(adonovan): fix: this assertion fires for interface methods.
|
||||
// The type of the receiver of an interface method is a named type
|
||||
// if the Package was loaded from export data, or an unnamed (interface)
|
||||
// type if the Package was produced by type-checking ASTs.
|
||||
// if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil {
|
||||
// return fmt.Errorf("receiver: %s", err)
|
||||
// }
|
||||
}
|
||||
case *types.Slice:
|
||||
y := y.(*types.Slice)
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("slice elements: %s", err)
|
||||
}
|
||||
case *types.Struct:
|
||||
y := y.(*types.Struct)
|
||||
if x.NumFields() != y.NumFields() {
|
||||
return fmt.Errorf("unequal struct fields: %d vs %d",
|
||||
x.NumFields(), y.NumFields())
|
||||
}
|
||||
for i := 0; i < x.NumFields(); i++ {
|
||||
xf := x.Field(i)
|
||||
yf := y.Field(i)
|
||||
if xf.Name() != yf.Name() {
|
||||
return fmt.Errorf("mismatched fields: %s vs %s", xf, yf)
|
||||
}
|
||||
if err := equalType(xf.Type(), yf.Type()); err != nil {
|
||||
return fmt.Errorf("struct field %s: %s", xf.Name(), err)
|
||||
}
|
||||
if x.Tag(i) != y.Tag(i) {
|
||||
return fmt.Errorf("struct field %s has unequal tags: %q vs %q",
|
||||
xf.Name(), x.Tag(i), y.Tag(i))
|
||||
}
|
||||
}
|
||||
case *types.Tuple:
|
||||
y := y.(*types.Tuple)
|
||||
if x.Len() != y.Len() {
|
||||
return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len())
|
||||
}
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil {
|
||||
return fmt.Errorf("tuple element %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestVeryLongFile tests the position of an import object declared in
|
||||
// a very long input file. Line numbers greater than maxlines are
|
||||
// reported as line 1, not garbage or token.NoPos.
|
||||
func TestVeryLongFile(t *testing.T) {
|
||||
// parse and typecheck
|
||||
longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int"
|
||||
fset1 := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset1, "foo.go", longFile, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var conf types.Config
|
||||
pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// export
|
||||
exportdata, err := gcimporter.BExportData(fset1, pkg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// import
|
||||
imports := make(map[string]*types.Package)
|
||||
fset2 := token.NewFileSet()
|
||||
_, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path())
|
||||
if err != nil {
|
||||
t.Fatalf("BImportData(%s): %v", pkg.Path(), err)
|
||||
}
|
||||
|
||||
// compare
|
||||
posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos())
|
||||
posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos())
|
||||
if want := "foo.go:1:1"; posn2.String() != want {
|
||||
t.Errorf("X position = %s, want %s (orig was %s)",
|
||||
posn2, want, posn1)
|
||||
}
|
||||
}
|
||||
|
||||
const src = `
|
||||
package p
|
||||
|
||||
type (
|
||||
T0 = int32
|
||||
T1 = struct{}
|
||||
T2 = struct{ T1 }
|
||||
Invalid = foo // foo is undeclared
|
||||
)
|
||||
`
|
||||
|
||||
func checkPkg(t *testing.T, pkg *types.Package, label string) {
|
||||
T1 := types.NewStruct(nil, nil)
|
||||
T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
typ types.Type
|
||||
}{
|
||||
{"T0", types.Typ[types.Int32]},
|
||||
{"T1", T1},
|
||||
{"T2", T2},
|
||||
{"Invalid", types.Typ[types.Invalid]},
|
||||
} {
|
||||
obj := pkg.Scope().Lookup(test.name)
|
||||
if obj == nil {
|
||||
t.Errorf("%s: %s not found", label, test.name)
|
||||
continue
|
||||
}
|
||||
tname, _ := obj.(*types.TypeName)
|
||||
if tname == nil {
|
||||
t.Errorf("%s: %v not a type name", label, obj)
|
||||
continue
|
||||
}
|
||||
if !tname.IsAlias() {
|
||||
t.Errorf("%s: %v: not marked as alias", label, tname)
|
||||
continue
|
||||
}
|
||||
if got := tname.Type(); !types.Identical(got, test.typ) {
|
||||
t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAliases(t *testing.T) {
|
||||
// parse and typecheck
|
||||
fset1 := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset1, "p.go", src, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var conf types.Config
|
||||
pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil)
|
||||
if err == nil {
|
||||
// foo in undeclared in src; we should see an error
|
||||
t.Fatal("invalid source type-checked without error")
|
||||
}
|
||||
if pkg1 == nil {
|
||||
// despite incorrect src we should see a (partially) type-checked package
|
||||
t.Fatal("nil package returned")
|
||||
}
|
||||
checkPkg(t, pkg1, "export")
|
||||
|
||||
// export
|
||||
exportdata, err := gcimporter.BExportData(fset1, pkg1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// import
|
||||
imports := make(map[string]*types.Package)
|
||||
fset2 := token.NewFileSet()
|
||||
_, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg1.Path())
|
||||
if err != nil {
|
||||
t.Fatalf("BImportData(%s): %v", pkg1.Path(), err)
|
||||
}
|
||||
checkPkg(t, pkg2, "import")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -204,14 +204,11 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func
|
|||
// Or, define a new standard go/types/gcexportdata package.
|
||||
fset := token.NewFileSet()
|
||||
|
||||
// The indexed export format starts with an 'i'; the older
|
||||
// binary export format starts with a 'c', 'd', or 'v'
|
||||
// (from "version"). Select appropriate importer.
|
||||
if len(data) > 0 && data[0] == 'i' {
|
||||
_, pkg, err = IImportData(fset, packages, data[1:], id)
|
||||
} else {
|
||||
_, pkg, err = BImportData(fset, packages, data, id)
|
||||
// The indexed export format starts with an 'i'.
|
||||
if len(data) == 0 || data[0] != 'i' {
|
||||
return nil, fmt.Errorf("unknown export data format")
|
||||
}
|
||||
_, pkg, err = IImportData(fset, packages, data[1:], id)
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unknown export data header: %q", hdr)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ package gcimporter
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
|
|
@ -25,6 +26,15 @@ import (
|
|||
// 0: Go1.11 encoding
|
||||
const iexportVersion = 0
|
||||
|
||||
// internalError represents an error generated inside this package.
|
||||
type internalError string
|
||||
|
||||
func (e internalError) Error() string { return "gcimporter: " + string(e) }
|
||||
|
||||
func internalErrorf(format string, args ...interface{}) error {
|
||||
return internalError(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// IExportData returns the binary export data for pkg.
|
||||
//
|
||||
// If no file set is provided, position info will be missing.
|
||||
|
|
@ -528,6 +538,16 @@ func constantToFloat(x constant.Value) *big.Float {
|
|||
return &f
|
||||
}
|
||||
|
||||
func valueToRat(x constant.Value) *big.Rat {
|
||||
// Convert little-endian to big-endian.
|
||||
// I can't believe this is necessary.
|
||||
bytes := constant.Bytes(x)
|
||||
for i := 0; i < len(bytes)/2; i++ {
|
||||
bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
|
||||
}
|
||||
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||
}
|
||||
|
||||
// mpint exports a multi-precision integer.
|
||||
//
|
||||
// For unsigned types, small values are written out as a single
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import (
|
|||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
var isRace = false
|
||||
|
||||
func TestIExportData_stdlib(t *testing.T) {
|
||||
if runtime.Compiler == "gccgo" {
|
||||
t.Skip("gccgo standard library is inaccessible")
|
||||
|
|
@ -178,6 +180,16 @@ func TestIExportData_long(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
const src = `
|
||||
package p
|
||||
type (
|
||||
T0 = int32
|
||||
T1 = struct{}
|
||||
T2 = struct{ T1 }
|
||||
Invalid = foo // foo is undeclared
|
||||
)
|
||||
`
|
||||
|
||||
func TestIExportData_typealiases(t *testing.T) {
|
||||
// parse and typecheck
|
||||
fset1 := token.NewFileSet()
|
||||
|
|
@ -307,3 +319,220 @@ func valueToRat(x constant.Value) *big.Rat {
|
|||
}
|
||||
return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
|
||||
}
|
||||
|
||||
func fileLine(fset *token.FileSet, obj types.Object) string {
|
||||
posn := fset.Position(obj.Pos())
|
||||
return fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
|
||||
}
|
||||
|
||||
// equalObj reports how x and y differ. They are assumed to belong to
|
||||
// different universes so cannot be compared directly.
|
||||
func equalObj(x, y types.Object) error {
|
||||
if reflect.TypeOf(x) != reflect.TypeOf(y) {
|
||||
return fmt.Errorf("%T vs %T", x, y)
|
||||
}
|
||||
xt := x.Type()
|
||||
yt := y.Type()
|
||||
switch x.(type) {
|
||||
case *types.Var, *types.Func:
|
||||
// ok
|
||||
case *types.Const:
|
||||
xval := x.(*types.Const).Val()
|
||||
yval := y.(*types.Const).Val()
|
||||
// Use string comparison for floating-point values since rounding is permitted.
|
||||
if constant.Compare(xval, token.NEQ, yval) &&
|
||||
!(xval.Kind() == constant.Float && xval.String() == yval.String()) {
|
||||
return fmt.Errorf("unequal constants %s vs %s", xval, yval)
|
||||
}
|
||||
case *types.TypeName:
|
||||
xt = xt.Underlying()
|
||||
yt = yt.Underlying()
|
||||
default:
|
||||
return fmt.Errorf("unexpected %T", x)
|
||||
}
|
||||
return equalType(xt, yt)
|
||||
}
|
||||
|
||||
func equalType(x, y types.Type) error {
|
||||
if reflect.TypeOf(x) != reflect.TypeOf(y) {
|
||||
return fmt.Errorf("unequal kinds: %T vs %T", x, y)
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case *types.Interface:
|
||||
y := y.(*types.Interface)
|
||||
// TODO(gri): enable separate emission of Embedded interfaces
|
||||
// and ExplicitMethods then use this logic.
|
||||
// if x.NumEmbeddeds() != y.NumEmbeddeds() {
|
||||
// return fmt.Errorf("unequal number of embedded interfaces: %d vs %d",
|
||||
// x.NumEmbeddeds(), y.NumEmbeddeds())
|
||||
// }
|
||||
// for i := 0; i < x.NumEmbeddeds(); i++ {
|
||||
// xi := x.Embedded(i)
|
||||
// yi := y.Embedded(i)
|
||||
// if xi.String() != yi.String() {
|
||||
// return fmt.Errorf("mismatched %th embedded interface: %s vs %s",
|
||||
// i, xi, yi)
|
||||
// }
|
||||
// }
|
||||
// if x.NumExplicitMethods() != y.NumExplicitMethods() {
|
||||
// return fmt.Errorf("unequal methods: %d vs %d",
|
||||
// x.NumExplicitMethods(), y.NumExplicitMethods())
|
||||
// }
|
||||
// for i := 0; i < x.NumExplicitMethods(); i++ {
|
||||
// xm := x.ExplicitMethod(i)
|
||||
// ym := y.ExplicitMethod(i)
|
||||
// if xm.Name() != ym.Name() {
|
||||
// return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym)
|
||||
// }
|
||||
// if err := equalType(xm.Type(), ym.Type()); err != nil {
|
||||
// return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
|
||||
// }
|
||||
// }
|
||||
if x.NumMethods() != y.NumMethods() {
|
||||
return fmt.Errorf("unequal methods: %d vs %d",
|
||||
x.NumMethods(), y.NumMethods())
|
||||
}
|
||||
for i := 0; i < x.NumMethods(); i++ {
|
||||
xm := x.Method(i)
|
||||
ym := y.Method(i)
|
||||
if xm.Name() != ym.Name() {
|
||||
return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym)
|
||||
}
|
||||
if err := equalType(xm.Type(), ym.Type()); err != nil {
|
||||
return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
|
||||
}
|
||||
}
|
||||
case *types.Array:
|
||||
y := y.(*types.Array)
|
||||
if x.Len() != y.Len() {
|
||||
return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len())
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("array elements: %s", err)
|
||||
}
|
||||
case *types.Basic:
|
||||
y := y.(*types.Basic)
|
||||
if x.Kind() != y.Kind() {
|
||||
return fmt.Errorf("unequal basic types: %s vs %s", x, y)
|
||||
}
|
||||
case *types.Chan:
|
||||
y := y.(*types.Chan)
|
||||
if x.Dir() != y.Dir() {
|
||||
return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir())
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("channel elements: %s", err)
|
||||
}
|
||||
case *types.Map:
|
||||
y := y.(*types.Map)
|
||||
if err := equalType(x.Key(), y.Key()); err != nil {
|
||||
return fmt.Errorf("map keys: %s", err)
|
||||
}
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("map values: %s", err)
|
||||
}
|
||||
case *types.Named:
|
||||
y := y.(*types.Named)
|
||||
if x.String() != y.String() {
|
||||
return fmt.Errorf("unequal named types: %s vs %s", x, y)
|
||||
}
|
||||
case *types.Pointer:
|
||||
y := y.(*types.Pointer)
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("pointer elements: %s", err)
|
||||
}
|
||||
case *types.Signature:
|
||||
y := y.(*types.Signature)
|
||||
if err := equalType(x.Params(), y.Params()); err != nil {
|
||||
return fmt.Errorf("parameters: %s", err)
|
||||
}
|
||||
if err := equalType(x.Results(), y.Results()); err != nil {
|
||||
return fmt.Errorf("results: %s", err)
|
||||
}
|
||||
if x.Variadic() != y.Variadic() {
|
||||
return fmt.Errorf("unequal variadicity: %t vs %t",
|
||||
x.Variadic(), y.Variadic())
|
||||
}
|
||||
if (x.Recv() != nil) != (y.Recv() != nil) {
|
||||
return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv())
|
||||
}
|
||||
if x.Recv() != nil {
|
||||
// TODO(adonovan): fix: this assertion fires for interface methods.
|
||||
// The type of the receiver of an interface method is a named type
|
||||
// if the Package was loaded from export data, or an unnamed (interface)
|
||||
// type if the Package was produced by type-checking ASTs.
|
||||
// if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil {
|
||||
// return fmt.Errorf("receiver: %s", err)
|
||||
// }
|
||||
}
|
||||
case *types.Slice:
|
||||
y := y.(*types.Slice)
|
||||
if err := equalType(x.Elem(), y.Elem()); err != nil {
|
||||
return fmt.Errorf("slice elements: %s", err)
|
||||
}
|
||||
case *types.Struct:
|
||||
y := y.(*types.Struct)
|
||||
if x.NumFields() != y.NumFields() {
|
||||
return fmt.Errorf("unequal struct fields: %d vs %d",
|
||||
x.NumFields(), y.NumFields())
|
||||
}
|
||||
for i := 0; i < x.NumFields(); i++ {
|
||||
xf := x.Field(i)
|
||||
yf := y.Field(i)
|
||||
if xf.Name() != yf.Name() {
|
||||
return fmt.Errorf("mismatched fields: %s vs %s", xf, yf)
|
||||
}
|
||||
if err := equalType(xf.Type(), yf.Type()); err != nil {
|
||||
return fmt.Errorf("struct field %s: %s", xf.Name(), err)
|
||||
}
|
||||
if x.Tag(i) != y.Tag(i) {
|
||||
return fmt.Errorf("struct field %s has unequal tags: %q vs %q",
|
||||
xf.Name(), x.Tag(i), y.Tag(i))
|
||||
}
|
||||
}
|
||||
case *types.Tuple:
|
||||
y := y.(*types.Tuple)
|
||||
if x.Len() != y.Len() {
|
||||
return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len())
|
||||
}
|
||||
for i := 0; i < x.Len(); i++ {
|
||||
if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil {
|
||||
return fmt.Errorf("tuple element %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPkg(t *testing.T, pkg *types.Package, label string) {
|
||||
T1 := types.NewStruct(nil, nil)
|
||||
T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
typ types.Type
|
||||
}{
|
||||
{"T0", types.Typ[types.Int32]},
|
||||
{"T1", T1},
|
||||
{"T2", T2},
|
||||
{"Invalid", types.Typ[types.Invalid]},
|
||||
} {
|
||||
obj := pkg.Scope().Lookup(test.name)
|
||||
if obj == nil {
|
||||
t.Errorf("%s: %s not found", label, test.name)
|
||||
continue
|
||||
}
|
||||
tname, _ := obj.(*types.TypeName)
|
||||
if tname == nil {
|
||||
t.Errorf("%s: %v not a type name", label, obj)
|
||||
continue
|
||||
}
|
||||
if !tname.IsAlias() {
|
||||
t.Errorf("%s: %v: not marked as alias", label, tname)
|
||||
continue
|
||||
}
|
||||
if got := tname.Type(); !types.Identical(got, test.typ) {
|
||||
t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import (
|
|||
"go/types"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type intReader struct {
|
||||
|
|
@ -25,6 +28,10 @@ type intReader struct {
|
|||
path string
|
||||
}
|
||||
|
||||
func errorf(format string, args ...interface{}) {
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (r *intReader) int64() int64 {
|
||||
i, err := binary.ReadVarint(r.Reader)
|
||||
if err != nil {
|
||||
|
|
@ -628,3 +635,166 @@ func (r *importReader) byte() byte {
|
|||
}
|
||||
return x
|
||||
}
|
||||
|
||||
const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go
|
||||
|
||||
// Synthesize a token.Pos
|
||||
type fakeFileSet struct {
|
||||
fset *token.FileSet
|
||||
files map[string]*token.File
|
||||
}
|
||||
|
||||
func (s *fakeFileSet) pos(file string, line, column int) token.Pos {
|
||||
// TODO(mdempsky): Make use of column.
|
||||
|
||||
// Since we don't know the set of needed file positions, we
|
||||
// reserve maxlines positions per file.
|
||||
const maxlines = 64 * 1024
|
||||
f := s.files[file]
|
||||
if f == nil {
|
||||
f = s.fset.AddFile(file, -1, maxlines)
|
||||
s.files[file] = f
|
||||
// Allocate the fake linebreak indices on first use.
|
||||
// TODO(adonovan): opt: save ~512KB using a more complex scheme?
|
||||
fakeLinesOnce.Do(func() {
|
||||
fakeLines = make([]int, maxlines)
|
||||
for i := range fakeLines {
|
||||
fakeLines[i] = i
|
||||
}
|
||||
})
|
||||
f.SetLines(fakeLines)
|
||||
}
|
||||
|
||||
if line > maxlines {
|
||||
line = 1
|
||||
}
|
||||
|
||||
// Treat the file as if it contained only newlines
|
||||
// and column=1: use the line number as the offset.
|
||||
return f.Pos(line - 1)
|
||||
}
|
||||
|
||||
var (
|
||||
fakeLines []int
|
||||
fakeLinesOnce sync.Once
|
||||
)
|
||||
|
||||
func chanDir(d int) types.ChanDir {
|
||||
// tag values must match the constants in cmd/compile/internal/gc/go.go
|
||||
switch d {
|
||||
case 1 /* Crecv */ :
|
||||
return types.RecvOnly
|
||||
case 2 /* Csend */ :
|
||||
return types.SendOnly
|
||||
case 3 /* Cboth */ :
|
||||
return types.SendRecv
|
||||
default:
|
||||
errorf("unexpected channel dir %d", d)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func exported(name string) bool {
|
||||
ch, _ := utf8.DecodeRuneInString(name)
|
||||
return unicode.IsUpper(ch)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Export format
|
||||
|
||||
// Tags. Must be < 0.
|
||||
const (
|
||||
// Objects
|
||||
packageTag = -(iota + 1)
|
||||
constTag
|
||||
typeTag
|
||||
varTag
|
||||
funcTag
|
||||
endTag
|
||||
|
||||
// Types
|
||||
namedTag
|
||||
arrayTag
|
||||
sliceTag
|
||||
dddTag
|
||||
structTag
|
||||
pointerTag
|
||||
signatureTag
|
||||
interfaceTag
|
||||
mapTag
|
||||
chanTag
|
||||
|
||||
// Values
|
||||
falseTag
|
||||
trueTag
|
||||
int64Tag
|
||||
floatTag
|
||||
fractionTag // not used by gc
|
||||
complexTag
|
||||
stringTag
|
||||
nilTag // only used by gc (appears in exported inlined function bodies)
|
||||
unknownTag // not used by gc (only appears in packages with errors)
|
||||
|
||||
// Type aliases
|
||||
aliasTag
|
||||
)
|
||||
|
||||
var predeclOnce sync.Once
|
||||
var predecl []types.Type // initialized lazily
|
||||
|
||||
func predeclared() []types.Type {
|
||||
predeclOnce.Do(func() {
|
||||
// initialize lazily to be sure that all
|
||||
// elements have been initialized before
|
||||
predecl = []types.Type{ // basic types
|
||||
types.Typ[types.Bool],
|
||||
types.Typ[types.Int],
|
||||
types.Typ[types.Int8],
|
||||
types.Typ[types.Int16],
|
||||
types.Typ[types.Int32],
|
||||
types.Typ[types.Int64],
|
||||
types.Typ[types.Uint],
|
||||
types.Typ[types.Uint8],
|
||||
types.Typ[types.Uint16],
|
||||
types.Typ[types.Uint32],
|
||||
types.Typ[types.Uint64],
|
||||
types.Typ[types.Uintptr],
|
||||
types.Typ[types.Float32],
|
||||
types.Typ[types.Float64],
|
||||
types.Typ[types.Complex64],
|
||||
types.Typ[types.Complex128],
|
||||
types.Typ[types.String],
|
||||
|
||||
// basic type aliases
|
||||
types.Universe.Lookup("byte").Type(),
|
||||
types.Universe.Lookup("rune").Type(),
|
||||
|
||||
// error
|
||||
types.Universe.Lookup("error").Type(),
|
||||
|
||||
// untyped types
|
||||
types.Typ[types.UntypedBool],
|
||||
types.Typ[types.UntypedInt],
|
||||
types.Typ[types.UntypedRune],
|
||||
types.Typ[types.UntypedFloat],
|
||||
types.Typ[types.UntypedComplex],
|
||||
types.Typ[types.UntypedString],
|
||||
types.Typ[types.UntypedNil],
|
||||
|
||||
// package unsafe
|
||||
types.Typ[types.UnsafePointer],
|
||||
|
||||
// invalid type
|
||||
types.Typ[types.Invalid], // only appears in packages with errors
|
||||
|
||||
// used internally by gc; never used by this package or in .a files
|
||||
anyType{},
|
||||
}
|
||||
})
|
||||
return predecl
|
||||
}
|
||||
|
||||
type anyType struct{}
|
||||
|
||||
func (t anyType) Underlying() types.Type { return t }
|
||||
func (t anyType) String() string { return "any" }
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue