go.tools/go/importer: simplified exporter/importer

By using a simple (graph-based) serialization algorithm
and binary encoding, a significantly more compact export
data format is achieved than what the current compilers
use. Furthermore, the exporter and importer are completely
symmetric algorithms that are compact, and much easier to
change/expand.

R=adonovan
CC=golang-dev
https://golang.org/cl/42960043
This commit is contained in:
Robert Griesemer 2013-12-16 14:28:17 -08:00
parent 91abc02562
commit 5eb4fdc120
3 changed files with 1056 additions and 0 deletions

442
go/importer/export.go Normal file
View File

@ -0,0 +1,442 @@
// Copyright 2013 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 importer
import (
"bytes"
"encoding/binary"
"fmt"
"go/ast"
"strings"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// debugging support
const (
debug = false // emit debugging data
trace = false // print emitted data
)
const (
magic = "\n$$ exports $$\n"
version = 0
)
// Object and type tags. Must be < 0.
const (
// Objects
_Package = -(iota + 1)
_Const
_Type
_Var
_Func
// Types
_Basic
_Array
_Slice
_Struct
_Pointer
_Tuple
_Signature
_Interface
_Map
_Chan
_Named
)
// ExportData serializes the interface (exported package objects)
// of package pkg and returns the corresponding data. The export
// format is described elsewhere (TODO).
func ExportData(pkg *types.Package) []byte {
p := exporter{
data: []byte(magic),
// TODO(gri) If we can't have nil packages
// or types, remove nil entries at index 0.
pkgIndex: map[*types.Package]int{nil: 0},
typIndex: map[types.Type]int{nil: 0},
}
// populate typIndex with predeclared types
for _, t := range types.Typ[1:] {
p.typIndex[t] = len(p.typIndex)
}
p.typIndex[types.Universe.Lookup("error").Type()] = len(p.typIndex)
if trace {
p.tracef("export %s\n", pkg.Name())
defer p.tracef("\n")
}
p.int(version)
p.pkg(pkg)
// collect exported objects from package scope
var list []types.Object
scope := pkg.Scope()
for _, name := range scope.Names() {
if exported(name) {
list = append(list, scope.Lookup(name))
}
}
// write objects
p.int(len(list))
for _, obj := range list {
p.obj(obj)
}
return p.data
}
type exporter struct {
data []byte
pkgIndex map[*types.Package]int
typIndex map[types.Type]int
// tracing support
indent string
}
func (p *exporter) pkg(pkg *types.Package) {
if trace {
p.tracef("package { ")
defer p.tracef("} ")
}
// if the package was seen before, write its index (>= 0)
if i, ok := p.pkgIndex[pkg]; ok {
p.int(i)
return
}
p.pkgIndex[pkg] = len(p.pkgIndex)
// otherwise, write the package tag (< 0) and package data
p.int(_Package)
p.string(pkg.Name())
p.string(pkg.Path())
}
func (p *exporter) obj(obj types.Object) {
if trace {
p.tracef("object %s {\n", obj.Name())
defer p.tracef("}\n")
}
switch obj := obj.(type) {
case *types.Const:
p.int(_Const)
p.string(obj.Name())
p.typ(obj.Type())
p.val(obj.Val())
case *types.TypeName:
p.int(_Type)
// name is written by corresponding named type
p.typ(obj.Type().(*types.Named))
case *types.Var:
p.int(_Var)
p.string(obj.Name())
p.typ(obj.Type())
case *types.Func:
p.int(_Func)
p.string(obj.Name())
p.signature(obj.Type().(*types.Signature))
default:
panic(fmt.Sprintf("unexpected object type %T", obj))
}
}
func (p *exporter) val(x exact.Value) {
if trace {
p.tracef("value { ")
defer p.tracef("} ")
}
kind := x.Kind()
p.int(int(kind))
switch kind {
case exact.Bool:
p.bool(exact.BoolVal(x))
case exact.String:
p.string(exact.StringVal(x))
case exact.Int:
p.intVal(x)
case exact.Float:
p.floatVal(x)
case exact.Complex:
p.floatVal(exact.Real(x))
p.floatVal(exact.Imag(x))
default:
panic(fmt.Sprintf("unexpected value kind %d", kind))
}
}
func (p *exporter) intVal(x exact.Value) {
sign := exact.Sign(x)
p.int(sign)
if sign != 0 {
p.bytes(exact.Bytes(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) typ(typ types.Type) {
if trace {
p.tracef("type {\n")
defer p.tracef("}\n")
}
// if the type was seen before, write its index (>= 0)
if i, ok := p.typIndex[typ]; ok {
p.int(i)
return
}
p.typIndex[typ] = len(p.typIndex)
// otherwise, write the type tag (< 0) and type data
switch t := typ.(type) {
case *types.Basic:
// Basic types are pre-recorded and don't usually end up here.
// However, the alias types byte and rune are not in the types.Typ
// table and get emitted here (once per package, if they appear).
// 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.string(t.Name())
case *types.Array:
p.int(_Array)
p.typ(t.Elem())
p.int64(t.Len())
case *types.Slice:
p.int(_Slice)
p.typ(t.Elem())
case *types.Struct:
p.int(_Struct)
n := t.NumFields()
p.int(n)
for i := 0; i < n; i++ {
p.field(t.Field(i))
p.string(t.Tag(i))
}
case *types.Pointer:
p.int(_Pointer)
p.typ(t.Elem())
case *types.Signature:
p.int(_Signature)
p.signature(t)
case *types.Interface:
p.int(_Interface)
n := t.NumMethods()
p.int(n)
for i := 0; i < n; i++ {
m := t.Method(i)
p.qualifiedName(m.Pkg(), m.Name())
p.signature(m.Type().(*types.Signature))
}
case *types.Map:
p.int(_Map)
p.typ(t.Key())
p.typ(t.Elem())
case *types.Chan:
p.int(_Chan)
p.int(int(t.Dir()))
p.typ(t.Elem())
case *types.Named:
p.int(_Named)
// write type object
obj := t.Obj()
p.string(obj.Name())
p.pkg(obj.Pkg())
// write underlying type
p.typ(t.Underlying())
// write associated methods
n := t.NumMethods()
p.int(n)
for i := 0; i < n; i++ {
m := t.Method(i)
p.string(m.Name())
p.signature(m.Type().(*types.Signature))
}
default:
panic("unreachable")
}
}
func (p *exporter) field(f *types.Var) {
// anonymous fields have "" name
name := ""
if !f.Anonymous() {
name = f.Name()
}
p.qualifiedName(f.Pkg(), name)
p.typ(f.Type())
}
func (p *exporter) qualifiedName(pkg *types.Package, name string) {
p.string(name)
// exported names don't write package
if !exported(name) {
if pkg == nil {
panic(fmt.Sprintf("nil package for unexported qualified name %s", name))
}
p.pkg(pkg)
}
}
func (p *exporter) signature(sig *types.Signature) {
// TODO(gri) We only need to record the receiver type
// for interface methods if we flatten them
// out. If we track embedded types instead,
// the information is already present.
if recv := sig.Recv(); recv != nil {
p.bool(true)
p.param(recv)
} else {
p.bool(false)
}
p.tuple(sig.Params())
p.tuple(sig.Results())
p.bool(sig.IsVariadic())
}
func (p *exporter) param(v *types.Var) {
p.string(v.Name())
p.typ(v.Type())
}
func (p *exporter) tuple(t *types.Tuple) {
n := t.Len()
p.int(n)
for i := 0; i < n; i++ {
p.param(t.At(i))
}
}
// ----------------------------------------------------------------------------
// encoders
func (p *exporter) bool(b bool) {
var x int64
if b {
x = 1
}
p.int64(x)
}
func (p *exporter) string(s string) {
// TODO(gri) consider inlining this to avoid an extra allocation
p.bytes([]byte(s))
}
func (p *exporter) int(x int) {
p.int64(int64(x))
}
func (p *exporter) int64(x int64) {
if debug {
p.marker('i')
}
if trace {
p.tracef("%d ", x)
}
p.rawInt64(x)
}
func (p *exporter) bytes(b []byte) {
if debug {
p.marker('b')
}
if trace {
p.tracef("%q ", b)
}
p.rawInt64(int64(len(b)))
if len(b) > 0 {
p.data = append(p.data, b...)
}
}
// marker emits a marker byte and position information which makes
// it easy for a reader to detect if it is "out of sync". Used in
// debug mode only.
func (p *exporter) marker(m byte) {
if debug {
p.data = append(p.data, m)
p.rawInt64(int64(len(p.data)))
}
}
// 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)
p.data = append(p.data, tmp[:n]...)
}
// utility functions
func (p *exporter) tracef(format string, args ...interface{}) {
// rewrite format string to take care of indentation
const indent = ". "
if strings.IndexAny(format, "{}\n") >= 0 {
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 += indent
case '}':
p.indent = p.indent[:len(p.indent)-len(indent)]
if i+1 < len(format) && format[i+1] == '\n' {
buf.WriteByte('\n')
buf.WriteString(p.indent)
buf.WriteString("} ")
i++
continue
}
}
buf.WriteByte(ch)
if ch == '\n' {
buf.WriteString(p.indent)
}
}
format = buf.String()
}
fmt.Printf(format, args...)
}
func exported(name string) bool {
return ast.IsExported(name)
}

417
go/importer/import.go Normal file
View File

@ -0,0 +1,417 @@
// Copyright 2013 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.
// This implementation is loosely based on the algorithm described
// in: "On the linearization of graphs and writing symbol files",
// by R. Griesemer, Technical Report 156, ETH Zürich, 1991.
// package importer implements an exporter and importer for Go export data.
package importer
import (
"encoding/binary"
"fmt"
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// ImportData imports a package from the serialized package data.
// If data is obviously malformed, an error is returned but in
// general it is not recommended to call ImportData on untrusted
// data.
func ImportData(imports map[string]*types.Package, data []byte) (*types.Package, error) {
// check magic string
if n := len(magic); len(data) < n || string(data[:n]) != magic {
return nil, fmt.Errorf("incorrect magic string: got %q; want %q", data[:n], magic)
}
p := importer{
data: data[len(magic):],
imports: imports,
pkgList: []*types.Package{nil},
typList: []types.Type{nil},
consumed: len(magic), // for debugging only
}
// populate typList with predeclared types
for _, t := range types.Typ[1:] {
p.typList = append(p.typList, t)
}
p.typList = append(p.typList, types.Universe.Lookup("error").Type())
if v := p.int(); v != version {
return nil, fmt.Errorf("unknown version: got %d; want %d", v, version)
}
pkg := p.pkg()
if debug && p.pkgList[1] != pkg {
panic("imported packaged not found in pkgList[1]")
}
// read objects
n := p.int()
for i := 0; i < n; i++ {
p.obj(pkg)
}
if len(p.data) > 0 {
return nil, fmt.Errorf("not all input data consumed")
}
// package was imported completely and without errors
pkg.MarkComplete()
return pkg, nil
}
type importer struct {
data []byte
imports map[string]*types.Package
pkgList []*types.Package
typList []types.Type
// debugging support
consumed int
}
func (p *importer) pkg() *types.Package {
// if the package was seen before, i is its index (>= 0)
i := p.int()
if i >= 0 {
return p.pkgList[i]
}
// otherwise, i is the package tag (< 0)
if i != _Package {
panic(fmt.Sprintf("unexpected package tag %d", i))
}
// read package data
name := p.string()
path := p.string()
// if the package was imported before, use that one; otherwise create a new one
pkg := p.imports[path]
if pkg == nil {
pkg = types.NewPackage(path, name, types.NewScope(nil))
p.imports[path] = pkg
}
p.pkgList = append(p.pkgList, pkg)
return pkg
}
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:
// type object is added to scope via respective named type
_ = p.typ().(*types.Named)
return
case _Var:
obj = types.NewVar(token.NoPos, pkg, p.string(), p.typ())
case _Func:
obj = types.NewFunc(token.NoPos, pkg, p.string(), p.signature())
default:
panic(fmt.Sprintf("unexpected object tag %d", tag))
}
if alt := pkg.Scope().Insert(obj); alt != nil {
panic(fmt.Sprintf("%s already declared", alt.Name()))
}
}
func (p *importer) val() exact.Value {
switch kind := exact.Kind(p.int()); kind {
case exact.Bool:
return exact.MakeBool(p.bool())
case exact.String:
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()
return exact.BinaryOp(re, token.ADD, exact.MakeImag(im))
default:
panic(fmt.Sprintf("unexpected value kind %d", kind))
}
}
func (p *importer) intVal() exact.Value {
sign := p.int()
var bytes []byte
if sign != 0 {
bytes = p.bytes()
}
x := exact.MakeFromBytes(bytes)
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)
}
return x
}
func (p *importer) record(t types.Type) {
p.typList = append(p.typList, t)
}
func (p *importer) typ() types.Type {
// if the type was seen before, i is its index (>= 0)
i := p.int()
if i >= 0 {
return p.typList[i]
}
// otherwise, i is the type tag (< 0)
switch i {
case _Basic:
t := types.Universe.Lookup(p.string()).(*types.TypeName).Type().(*types.Basic)
p.record(t)
return t
case _Array:
t := new(types.Array)
p.record(t)
*t = *types.NewArray(p.typ(), p.int64())
return t
case _Slice:
t := new(types.Slice)
p.record(t)
*t = *types.NewSlice(p.typ())
return t
case _Struct:
t := new(types.Struct)
p.record(t)
n := p.int()
fields := make([]*types.Var, n)
tags := make([]string, n)
for i := range fields {
fields[i] = p.field()
tags[i] = p.string()
}
*t = *types.NewStruct(fields, tags)
return t
case _Pointer:
t := new(types.Pointer)
p.record(t)
*t = *types.NewPointer(p.typ())
return t
case _Signature:
t := new(types.Signature)
p.record(t)
*t = *p.signature()
return t
case _Interface:
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())
}
*t = *types.NewInterface(methods, nil)
return t
case _Map:
t := new(types.Map)
p.record(t)
*t = *types.NewMap(p.typ(), p.typ())
return t
case _Chan:
t := new(types.Chan)
p.record(t)
*t = *types.NewChan(ast.ChanDir(p.int()), p.typ())
return t
case _Named:
// import type object
name := p.string()
pkg := p.pkg()
scope := pkg.Scope()
obj := scope.Lookup(name)
if obj == nil {
new := types.NewTypeName(token.NoPos, pkg, name, nil)
types.NewNamed(new, nil, nil)
scope.Insert(new)
obj = new
}
t := obj.Type().(*types.Named)
p.record(t)
// import underlying type
u := p.typ()
if t.Underlying() == nil {
t.SetUnderlying(u)
}
// read associated methods
n := p.int()
for i := 0; i < n; i++ {
t.AddMethod(types.NewFunc(token.NoPos, pkg, p.string(), p.signature()))
}
return t
default:
panic(fmt.Sprintf("unexpected type tag %d", i))
}
}
func deref(typ types.Type) types.Type {
if p, _ := typ.(*types.Pointer); p != nil {
return p.Elem()
}
return typ
}
func (p *importer) field() *types.Var {
pkg, name := p.qualifiedName()
typ := p.typ()
anonymous := false
if name == "" {
// anonymous field - typ must be T or *T and T must be a type name
switch typ := deref(typ).(type) {
case *types.Basic: // basic types are named types
pkg = nil
name = typ.Name()
case *types.Named:
obj := typ.Obj()
pkg = obj.Pkg() // TODO(gri) is this still correct?
name = obj.Name()
default:
panic("anonymous field expected")
}
anonymous = true
}
return types.NewField(token.NoPos, pkg, name, typ, anonymous)
}
func (p *importer) qualifiedName() (*types.Package, string) {
name := p.string()
pkg := p.pkgList[1] // exported names assume current package
if !exported(name) {
pkg = p.pkg()
if pkg == nil {
panic(fmt.Sprintf("nil package for unexported qualified name %q", name))
}
}
return pkg, name
}
func (p *importer) signature() *types.Signature {
var recv *types.Var
if p.bool() {
recv = p.param()
}
return types.NewSignature(nil, recv, p.tuple(), p.tuple(), p.bool())
}
func (p *importer) param() *types.Var {
return types.NewVar(token.NoPos, nil, p.string(), p.typ())
}
func (p *importer) tuple() *types.Tuple {
vars := make([]*types.Var, p.int())
for i := range vars {
vars[i] = p.param()
}
return types.NewTuple(vars...)
}
// ----------------------------------------------------------------------------
// decoders
func (p *importer) bool() bool {
return p.int64() != 0
}
func (p *importer) string() string {
return string(p.bytes())
}
func (p *importer) int() int {
return int(p.int64())
}
func (p *importer) int64() int64 {
if debug {
p.marker('i')
}
return p.rawInt64()
}
// Note: bytes() returns the respective byte slice w/o copy.
func (p *importer) bytes() []byte {
if debug {
p.marker('b')
}
var b []byte
if n := int(p.rawInt64()); n > 0 {
b = p.data[:n]
p.data = p.data[n:]
if debug {
p.consumed += n
}
}
return b
}
func (p *importer) marker(want byte) {
if debug {
if got := p.data[0]; got != want {
panic(fmt.Sprintf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.consumed))
}
p.data = p.data[1:]
p.consumed++
pos := p.consumed
if n := int(p.rawInt64()); n != pos {
panic(fmt.Sprintf("incorrect position: got %d; want %d", n, pos))
}
}
}
// rawInt64 should only be used by low-level decoders
func (p *importer) rawInt64() int64 {
i, n := binary.Varint(p.data)
p.data = p.data[n:]
if debug {
p.consumed += n
}
return i
}

197
go/importer/import_test.go Normal file
View File

@ -0,0 +1,197 @@
// Copyright 2013 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 importer
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"path/filepath"
"testing"
_ "code.google.com/p/go.tools/go/gcimporter"
"code.google.com/p/go.tools/go/types"
)
var tests = []string{
`package p`,
// consts
`package p; const X = true`,
`package p; const X, y, Z = true, false, 0 != 0`,
`package p; const ( A float32 = 1<<iota; B; C; D)`,
`package p; const X = "foo"`,
`package p; const X string = "foo"`,
`package p; const X = 0`,
`package p; const X = -42`,
`package p; const X = 3.14159265`,
`package p; const X = -1e-10`,
`package p; const X = 1.2 + 2.3i`,
`package p; const X = -1i`,
`package p; import "math"; const Pi = math.Pi`,
`package p; import m "math"; const Pi = m.Pi`,
// types
`package p; type T int`,
`package p; type T [10]int`,
`package p; type T []int`,
`package p; type T struct{}`,
`package p; type T struct{x int}`,
`package p; type T *int`,
`package p; type T func()`,
`package p; type T *T`,
`package p; type T interface{}`,
`package p; type T interface{ foo() }`,
`package p; type T interface{ m() T }`,
// TODO(gri) disabled for now - import/export works but
// types.Type.String() used in the test cannot handle cases
// like this yet
// `package p; type T interface{ m() interface{T} }`,
`package p; type T map[string]bool`,
`package p; type T chan int`,
`package p; type T <-chan complex64`,
`package p; type T chan<- map[int]string`,
// vars
`package p; var X int`,
`package p; var X, Y, Z struct{f int "tag"}`,
// funcs
`package p; func F()`,
`package p; func F(x int, y struct{}) bool`,
`package p; type T int; func (*T) F(x int, y struct{}) T`,
// selected special cases
`package p; type T int`,
`package p; type T uint8`,
`package p; type T byte`,
`package p; type T error`,
`package p; import "net/http"; type T http.Client`,
`package p; import "net/http"; type ( T1 http.Client; T2 struct { http.Client } )`,
`package p; import "unsafe"; type ( T1 unsafe.Pointer; T2 unsafe.Pointer )`,
`package p; import "unsafe"; type T struct { p unsafe.Pointer }`,
}
func TestImportSrc(t *testing.T) {
for _, src := range tests {
pkg, err := pkgForSource(src)
if err != nil {
t.Errorf("typecheck failed: %s", err)
continue
}
testExportImport(t, pkg)
}
}
// TODO(gri) expand to std library
var libs = []string{
"../exact",
"../gcimporter",
"../importer",
"../types",
"../types/typemap",
}
func TestImportLib(t *testing.T) {
for _, lib := range libs {
pkg, err := pkgFor(lib)
if err != nil {
t.Errorf("typecheck failed: %s", err)
continue
}
testExportImport(t, pkg)
}
}
func testExportImport(t *testing.T, pkg0 *types.Package) {
data := ExportData(pkg0)
imports := make(map[string]*types.Package)
pkg1, err := ImportData(imports, data)
if err != nil {
t.Errorf("package %s: import failed: %s", pkg0.Name(), err)
return
}
s0 := pkgString(pkg0)
s1 := pkgString(pkg1)
if s1 != s0 {
t.Errorf("package %s: \ngot:\n%s\nwant:\n%s\n", pkg0.Name(), s1, s0)
}
}
func pkgForSource(src string) (*types.Package, error) {
// parse file
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
return nil, err
}
// typecheck file
return types.Check("import-test", fset, []*ast.File{f})
}
func pkgFor(path string) (*types.Package, error) {
// collect filenames
ctxt := build.Default
pkginfo, err := ctxt.ImportDir(path, 0)
if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
return nil, err
}
filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
// parse files
fset := token.NewFileSet()
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)
if err != nil {
return nil, err
}
}
// typecheck files
return types.Check(path, fset, files)
}
func pkgString(pkg *types.Package) string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "package %s\n", pkg.Name())
scope := pkg.Scope()
for _, name := range scope.Names() {
if exported(name) {
obj := scope.Lookup(name)
buf.WriteString(obj.String())
switch obj := obj.(type) {
case *types.Const:
fmt.Fprintf(&buf, " = %s", obj.Val())
case *types.TypeName:
// 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")
for i := 0; i < n; i++ {
fmt.Fprintf(&buf, "\t%s\n", typ.Method(i))
}
buf.WriteString(")")
}
}
}
buf.WriteByte('\n')
}
}
return buf.String()
}