| // 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", |
| } |