From fe3445e822d021942dd58cd4e2ed5cc833103d8a Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 9 Oct 2015 13:35:07 -0700 Subject: [PATCH] x/tools/cmd/godex: prepare sources for switch to 1.5 go/types - copied all affected xxx.go files to a respective xxx14.go file - added build tags - no other source changes Change-Id: Ic150a097e537ee29a0e0616ab8e1e74b21e3066d Reviewed-on: https://go-review.googlesource.com/15702 Reviewed-by: Alan Donovan --- cmd/godex/gc.go | 2 + cmd/godex/gc14.go | 17 ++ cmd/godex/gccgo.go | 2 + cmd/godex/gccgo14.go | 42 +++++ cmd/godex/godex.go | 2 + cmd/godex/godex14.go | 209 ++++++++++++++++++++++ cmd/godex/print.go | 2 + cmd/godex/print14.go | 370 +++++++++++++++++++++++++++++++++++++++ cmd/godex/source.go | 2 + cmd/godex/source14.go | 21 +++ cmd/godex/writetype.go | 2 + cmd/godex/writetype14.go | 244 ++++++++++++++++++++++++++ 12 files changed, 915 insertions(+) create mode 100644 cmd/godex/gc14.go create mode 100644 cmd/godex/gccgo14.go create mode 100644 cmd/godex/godex14.go create mode 100644 cmd/godex/print14.go create mode 100644 cmd/godex/source14.go create mode 100644 cmd/godex/writetype14.go diff --git a/cmd/godex/gc.go b/cmd/godex/gc.go index 85335b92..e2529009 100644 --- a/cmd/godex/gc.go +++ b/cmd/godex/gc.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + // This file implements access to gc-generated export data. package main diff --git a/cmd/godex/gc14.go b/cmd/godex/gc14.go new file mode 100644 index 00000000..40c433d2 --- /dev/null +++ b/cmd/godex/gc14.go @@ -0,0 +1,17 @@ +// Copyright 2014 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. + +// +build !go1.5 + +// This file implements access to gc-generated export data. + +package main + +import ( + "golang.org/x/tools/go/gcimporter" +) + +func init() { + register("gc", gcimporter.Import) +} diff --git a/cmd/godex/gccgo.go b/cmd/godex/gccgo.go index aee2d8ee..2f2168da 100644 --- a/cmd/godex/gccgo.go +++ b/cmd/godex/gccgo.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + // This file implements access to gccgo-generated export data. package main diff --git a/cmd/godex/gccgo14.go b/cmd/godex/gccgo14.go new file mode 100644 index 00000000..13175278 --- /dev/null +++ b/cmd/godex/gccgo14.go @@ -0,0 +1,42 @@ +// Copyright 2014 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. + +// +build !go1.5 + +// This file implements access to gccgo-generated export data. + +package main + +import ( + "golang.org/x/tools/go/gccgoimporter" + "golang.org/x/tools/go/types" +) + +var ( + initmap = make(map[*types.Package]gccgoimporter.InitData) +) + +func init() { + incpaths := []string{"/"} + + // importer for default gccgo + var inst gccgoimporter.GccgoInstallation + inst.InitFromDriver("gccgo") + register("gccgo", inst.GetImporter(incpaths, initmap)) +} + +// Print the extra gccgo compiler data for this package, if it exists. +func (p *printer) printGccgoExtra(pkg *types.Package) { + if initdata, ok := initmap[pkg]; ok { + p.printf("/*\npriority %d\n", initdata.Priority) + + p.printDecl("init", len(initdata.Inits), func() { + for _, init := range initdata.Inits { + p.printf("%s %s %d\n", init.Name, init.InitFunc, init.Priority) + } + }) + + p.print("*/\n") + } +} diff --git a/cmd/godex/godex.go b/cmd/godex/godex.go index dee09904..3cdca2ec 100644 --- a/cmd/godex/godex.go +++ b/cmd/godex/godex.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + package main import ( diff --git a/cmd/godex/godex14.go b/cmd/godex/godex14.go new file mode 100644 index 00000000..c193bad3 --- /dev/null +++ b/cmd/godex/godex14.go @@ -0,0 +1,209 @@ +// Copyright 2014 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. + +// +build !go1.5 + +package main + +import ( + "errors" + "flag" + "fmt" + "go/build" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/go/types" +) + +var ( + source = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers") + verbose = flag.Bool("v", false, "verbose mode") +) + +// lists of registered sources and corresponding importers +var ( + sources []string + importers []types.Importer + importFailed = errors.New("import failed") +) + +// map of imported packages +var packages = make(map[string]*types.Package) + +func usage() { + fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}") + flag.PrintDefaults() + os.Exit(2) +} + +func report(msg string) { + fmt.Fprintln(os.Stderr, "error: "+msg) + os.Exit(2) +} + +func main() { + flag.Usage = usage + flag.Parse() + + if flag.NArg() == 0 { + report("no package name, path, or file provided") + } + + imp := tryImports + if *source != "" { + imp = lookup(*source) + if imp == nil { + report("source (-s argument) must be one of: " + strings.Join(sources, ", ")) + } + } + + for _, arg := range flag.Args() { + path, name := splitPathIdent(arg) + logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name) + + // generate possible package path prefixes + // (at the moment we do this for each argument - should probably cache the generated prefixes) + prefixes := make(chan string) + go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path)) + + // import package + pkg, err := tryPrefixes(packages, prefixes, path, imp) + if err != nil { + logf("\t=> ignoring %q: %s\n", path, err) + continue + } + + // filter objects if needed + var filter func(types.Object) bool + if name != "" { + filter = func(obj types.Object) bool { + // TODO(gri) perhaps use regular expression matching here? + return obj.Name() == name + } + } + + // print contents + print(os.Stdout, pkg, filter) + } +} + +func logf(format string, args ...interface{}) { + if *verbose { + fmt.Fprintf(os.Stderr, format, args...) + } +} + +// splitPathIdent splits a path.name argument into its components. +// All but the last path element may contain dots. +func splitPathIdent(arg string) (path, name string) { + if i := strings.LastIndex(arg, "."); i >= 0 { + if j := strings.LastIndex(arg, "/"); j < i { + // '.' is not part of path + path = arg[:i] + name = arg[i+1:] + return + } + } + path = arg + return +} + +// tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp +// by prepending all possible prefixes to path. It returns with the first package that it could import, or +// with an error. +func tryPrefixes(packages map[string]*types.Package, prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) { + for prefix := range prefixes { + actual := path + if prefix == "" { + // don't use filepath.Join as it will sanitize the path and remove + // a leading dot and then the path is not recognized as a relative + // package path by the importers anymore + logf("\ttrying no prefix\n") + } else { + actual = filepath.Join(prefix, path) + logf("\ttrying prefix %q\n", prefix) + } + pkg, err = imp(packages, actual) + if err == nil { + break + } + logf("\t=> importing %q failed: %s\n", actual, err) + } + return +} + +// tryImports is an importer that tries all registered importers +// successively until one of them succeeds or all of them failed. +func tryImports(packages map[string]*types.Package, path string) (pkg *types.Package, err error) { + for i, imp := range importers { + logf("\t\ttrying %s import\n", sources[i]) + pkg, err = imp(packages, path) + if err == nil { + break + } + logf("\t\t=> %s import failed: %s\n", sources[i], err) + } + return +} + +// protect protects an importer imp from panics and returns the protected importer. +func protect(imp types.Importer) types.Importer { + return func(packages map[string]*types.Package, path string) (pkg *types.Package, err error) { + defer func() { + if recover() != nil { + pkg = nil + err = importFailed + } + }() + return imp(packages, path) + } +} + +// register registers an importer imp for a given source src. +func register(src string, imp types.Importer) { + if lookup(src) != nil { + panic(src + " importer already registered") + } + sources = append(sources, src) + importers = append(importers, protect(imp)) +} + +// lookup returns the importer imp for a given source src. +func lookup(src string) types.Importer { + for i, s := range sources { + if s == src { + return importers[i] + } + } + return nil +} + +func genPrefixes(out chan string, all bool) { + out <- "" + if all { + platform := build.Default.GOOS + "_" + build.Default.GOARCH + dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...) + for _, dirname := range dirnames { + walkDir(filepath.Join(dirname, "pkg", platform), "", out) + } + } + close(out) +} + +func walkDir(dirname, prefix string, out chan string) { + fiList, err := ioutil.ReadDir(dirname) + if err != nil { + return + } + for _, fi := range fiList { + if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") { + prefix := filepath.Join(prefix, fi.Name()) + out <- prefix + walkDir(filepath.Join(dirname, fi.Name()), prefix, out) + } + } +} diff --git a/cmd/godex/print.go b/cmd/godex/print.go index e519f41c..d411afdd 100644 --- a/cmd/godex/print.go +++ b/cmd/godex/print.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + package main import ( diff --git a/cmd/godex/print14.go b/cmd/godex/print14.go new file mode 100644 index 00000000..55d7c5b6 --- /dev/null +++ b/cmd/godex/print14.go @@ -0,0 +1,370 @@ +// Copyright 2014 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. + +// +build !go1.5 + +package main + +import ( + "bytes" + "fmt" + "go/token" + "io" + "math/big" + + "golang.org/x/tools/go/exact" + "golang.org/x/tools/go/types" +) + +// TODO(gri) use tabwriter for alignment? + +func print(w io.Writer, pkg *types.Package, filter func(types.Object) bool) { + var p printer + p.pkg = pkg + p.printPackage(pkg, filter) + p.printGccgoExtra(pkg) + io.Copy(w, &p.buf) +} + +type printer struct { + pkg *types.Package + buf bytes.Buffer + indent int // current indentation level + last byte // last byte written +} + +func (p *printer) print(s string) { + // Write the string one byte at a time. We care about the presence of + // newlines for indentation which we will see even in the presence of + // (non-corrupted) Unicode; no need to read one rune at a time. + for i := 0; i < len(s); i++ { + ch := s[i] + if ch != '\n' && p.last == '\n' { + // Note: This could lead to a range overflow for very large + // indentations, but it's extremely unlikely to happen for + // non-pathological code. + p.buf.WriteString("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"[:p.indent]) + } + p.buf.WriteByte(ch) + p.last = ch + } +} + +func (p *printer) printf(format string, args ...interface{}) { + p.print(fmt.Sprintf(format, args...)) +} + +// methodsFor returns the named type and corresponding methods if the type +// denoted by obj is not an interface and has methods. Otherwise it returns +// the zero value. +func methodsFor(obj *types.TypeName) (*types.Named, []*types.Selection) { + named, _ := obj.Type().(*types.Named) + if named == nil { + // A type name's type can also be the + // exported basic type unsafe.Pointer. + return nil, nil + } + if _, ok := named.Underlying().(*types.Interface); ok { + // ignore interfaces + return nil, nil + } + methods := combinedMethodSet(named) + if len(methods) == 0 { + return nil, nil + } + return named, methods +} + +func (p *printer) printPackage(pkg *types.Package, filter func(types.Object) bool) { + // collect objects by kind + var ( + consts []*types.Const + typem []*types.Named // non-interface types with methods + typez []*types.TypeName // interfaces or types without methods + vars []*types.Var + funcs []*types.Func + builtins []*types.Builtin + methods = make(map[*types.Named][]*types.Selection) // method sets for named types + ) + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Exported() { + // collect top-level exported and possibly filtered objects + if filter == nil || filter(obj) { + switch obj := obj.(type) { + case *types.Const: + consts = append(consts, obj) + case *types.TypeName: + // group into types with methods and types without + if named, m := methodsFor(obj); named != nil { + typem = append(typem, named) + methods[named] = m + } else { + typez = append(typez, obj) + } + case *types.Var: + vars = append(vars, obj) + case *types.Func: + funcs = append(funcs, obj) + case *types.Builtin: + // for unsafe.Sizeof, etc. + builtins = append(builtins, obj) + } + } + } else if filter == nil { + // no filtering: collect top-level unexported types with methods + if obj, _ := obj.(*types.TypeName); obj != nil { + // see case *types.TypeName above + if named, m := methodsFor(obj); named != nil { + typem = append(typem, named) + methods[named] = m + } + } + } + } + + p.printf("package %s // %q\n", pkg.Name(), pkg.Path()) + + p.printDecl("const", len(consts), func() { + for _, obj := range consts { + p.printObj(obj) + p.print("\n") + } + }) + + p.printDecl("var", len(vars), func() { + for _, obj := range vars { + p.printObj(obj) + p.print("\n") + } + }) + + p.printDecl("type", len(typez), func() { + for _, obj := range typez { + p.printf("%s ", obj.Name()) + p.writeType(p.pkg, obj.Type().Underlying()) + p.print("\n") + } + }) + + // non-interface types with methods + for _, named := range typem { + first := true + if obj := named.Obj(); obj.Exported() { + if first { + p.print("\n") + first = false + } + p.printf("type %s ", obj.Name()) + p.writeType(p.pkg, named.Underlying()) + p.print("\n") + } + for _, m := range methods[named] { + if obj := m.Obj(); obj.Exported() { + if first { + p.print("\n") + first = false + } + p.printFunc(m.Recv(), obj.(*types.Func)) + p.print("\n") + } + } + } + + if len(funcs) > 0 { + p.print("\n") + for _, obj := range funcs { + p.printFunc(nil, obj) + p.print("\n") + } + } + + // TODO(gri) better handling of builtins (package unsafe only) + if len(builtins) > 0 { + p.print("\n") + for _, obj := range builtins { + p.printf("func %s() // builtin\n", obj.Name()) + } + } + + p.print("\n") +} + +func (p *printer) printDecl(keyword string, n int, printGroup func()) { + switch n { + case 0: + // nothing to do + case 1: + p.printf("\n%s ", keyword) + printGroup() + default: + p.printf("\n%s (\n", keyword) + p.indent++ + printGroup() + p.indent-- + p.print(")\n") + } +} + +// absInt returns the absolute value of v as a *big.Int. +// v must be a numeric value. +func absInt(v exact.Value) *big.Int { + // compute big-endian representation of v + b := exact.Bytes(v) // little-endian + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + return new(big.Int).SetBytes(b) +} + +var ( + one = big.NewRat(1, 1) + ten = big.NewRat(10, 1) +) + +// floatString returns the string representation for a +// numeric value v in normalized floating-point format. +func floatString(v exact.Value) string { + if exact.Sign(v) == 0 { + return "0.0" + } + // x != 0 + + // convert |v| into a big.Rat x + x := new(big.Rat).SetFrac(absInt(exact.Num(v)), absInt(exact.Denom(v))) + + // normalize x and determine exponent e + // (This is not very efficient, but also not speed-critical.) + var e int + for x.Cmp(ten) >= 0 { + x.Quo(x, ten) + e++ + } + for x.Cmp(one) < 0 { + x.Mul(x, ten) + e-- + } + + // TODO(gri) Values such as 1/2 are easier to read in form 0.5 + // rather than 5.0e-1. Similarly, 1.0e1 is easier to read as + // 10.0. Fine-tune best exponent range for readability. + + s := x.FloatString(100) // good-enough precision + + // trim trailing 0's + i := len(s) + for i > 0 && s[i-1] == '0' { + i-- + } + s = s[:i] + + // add a 0 if the number ends in decimal point + if len(s) > 0 && s[len(s)-1] == '.' { + s += "0" + } + + // add exponent and sign + if e != 0 { + s += fmt.Sprintf("e%+d", e) + } + if exact.Sign(v) < 0 { + s = "-" + s + } + + // TODO(gri) If v is a "small" fraction (i.e., numerator and denominator + // are just a small number of decimal digits), add the exact fraction as + // a comment. For instance: 3.3333...e-1 /* = 1/3 */ + + return s +} + +// valString returns the string representation for the value v. +// Setting floatFmt forces an integer value to be formatted in +// normalized floating-point format. +// TODO(gri) Move this code into package exact. +func valString(v exact.Value, floatFmt bool) string { + switch v.Kind() { + case exact.Int: + if floatFmt { + return floatString(v) + } + case exact.Float: + return floatString(v) + case exact.Complex: + re := exact.Real(v) + im := exact.Imag(v) + var s string + if exact.Sign(re) != 0 { + s = floatString(re) + if exact.Sign(im) >= 0 { + s += " + " + } else { + s += " - " + im = exact.UnaryOp(token.SUB, im, 0) // negate im + } + } + // im != 0, otherwise v would be exact.Int or exact.Float + return s + floatString(im) + "i" + } + return v.String() +} + +func (p *printer) printObj(obj types.Object) { + p.print(obj.Name()) + + typ, basic := obj.Type().Underlying().(*types.Basic) + if basic && typ.Info()&types.IsUntyped != 0 { + // don't write untyped types + } else { + p.print(" ") + p.writeType(p.pkg, obj.Type()) + } + + if obj, ok := obj.(*types.Const); ok { + floatFmt := basic && typ.Info()&(types.IsFloat|types.IsComplex) != 0 + p.print(" = ") + p.print(valString(obj.Val(), floatFmt)) + } +} + +func (p *printer) printFunc(recvType types.Type, obj *types.Func) { + p.print("func ") + sig := obj.Type().(*types.Signature) + if recvType != nil { + p.print("(") + p.writeType(p.pkg, recvType) + p.print(") ") + } + p.print(obj.Name()) + p.writeSignature(p.pkg, sig) +} + +// combinedMethodSet returns the method set for a named type T +// merged with all the methods of *T that have different names than +// the methods of T. +// +// combinedMethodSet is analogous to types/typeutil.IntuitiveMethodSet +// but doesn't require a MethodSetCache. +// TODO(gri) If this functionality doesn't change over time, consider +// just calling IntuitiveMethodSet eventually. +func combinedMethodSet(T *types.Named) []*types.Selection { + // method set for T + mset := types.NewMethodSet(T) + var res []*types.Selection + for i, n := 0, mset.Len(); i < n; i++ { + res = append(res, mset.At(i)) + } + + // add all *T methods with names different from T methods + pmset := types.NewMethodSet(types.NewPointer(T)) + for i, n := 0, pmset.Len(); i < n; i++ { + pm := pmset.At(i) + if obj := pm.Obj(); mset.Lookup(obj.Pkg(), obj.Name()) == nil { + res = append(res, pm) + } + } + + return res +} diff --git a/cmd/godex/source.go b/cmd/godex/source.go index 22d78132..a86031a0 100644 --- a/cmd/godex/source.go +++ b/cmd/godex/source.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + // This file implements access to export data from source. package main diff --git a/cmd/godex/source14.go b/cmd/godex/source14.go new file mode 100644 index 00000000..620a9b35 --- /dev/null +++ b/cmd/godex/source14.go @@ -0,0 +1,21 @@ +// Copyright 2014 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. + +// +build !go1.5 + +// This file implements access to export data from source. + +package main + +import ( + "golang.org/x/tools/go/types" +) + +func init() { + register("source", sourceImporter) +} + +func sourceImporter(packages map[string]*types.Package, path string) (*types.Package, error) { + panic("unimplemented") +} diff --git a/cmd/godex/writetype.go b/cmd/godex/writetype.go index 10c8e655..c0798550 100644 --- a/cmd/godex/writetype.go +++ b/cmd/godex/writetype.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build go1.5 + // This file implements writing of types. The functionality is lifted // directly from go/types, but now contains various modifications for // nicer output. diff --git a/cmd/godex/writetype14.go b/cmd/godex/writetype14.go new file mode 100644 index 00000000..ea009b4b --- /dev/null +++ b/cmd/godex/writetype14.go @@ -0,0 +1,244 @@ +// Copyright 2014 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. + +// +build !go1.5 + +// This file implements writing of types. The functionality is lifted +// directly from go/types, but now contains various modifications for +// nicer output. +// +// TODO(gri) back-port once we have a fixed interface and once the +// go/types API is not frozen anymore for the 1.3 release; and remove +// this implementation if possible. + +package main + +import "golang.org/x/tools/go/types" + +func (p *printer) writeType(this *types.Package, typ types.Type) { + p.writeTypeInternal(this, typ, make([]types.Type, 8)) +} + +// From go/types - leave for now to ease back-porting this code. +const GcCompatibilityMode = false + +func (p *printer) writeTypeInternal(this *types.Package, typ types.Type, visited []types.Type) { + // Theoretically, this is a quadratic lookup algorithm, but in + // practice deeply nested composite types with unnamed component + // types are uncommon. This code is likely more efficient than + // using a map. + for _, t := range visited { + if t == typ { + p.printf("○%T", typ) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + p.print("") + + case *types.Basic: + if t.Kind() == types.UnsafePointer { + p.print("unsafe.") + } + if GcCompatibilityMode { + // forget the alias names + switch t.Kind() { + case types.Byte: + t = types.Typ[types.Uint8] + case types.Rune: + t = types.Typ[types.Int32] + } + } + p.print(t.Name()) + + case *types.Array: + p.printf("[%d]", t.Len()) + p.writeTypeInternal(this, t.Elem(), visited) + + case *types.Slice: + p.print("[]") + p.writeTypeInternal(this, t.Elem(), visited) + + case *types.Struct: + n := t.NumFields() + if n == 0 { + p.print("struct{}") + return + } + + p.print("struct {\n") + p.indent++ + for i := 0; i < n; i++ { + f := t.Field(i) + if !f.Anonymous() { + p.printf("%s ", f.Name()) + } + p.writeTypeInternal(this, f.Type(), visited) + if tag := t.Tag(i); tag != "" { + p.printf(" %q", tag) + } + p.print("\n") + } + p.indent-- + p.print("}") + + case *types.Pointer: + p.print("*") + p.writeTypeInternal(this, t.Elem(), visited) + + case *types.Tuple: + p.writeTuple(this, t, false, visited) + + case *types.Signature: + p.print("func") + p.writeSignatureInternal(this, t, visited) + + case *types.Interface: + // We write the source-level methods and embedded types rather + // than the actual method set since resolved method signatures + // may have non-printable cycles if parameters have anonymous + // interface types that (directly or indirectly) embed the + // current interface. For instance, consider the result type + // of m: + // + // type T interface{ + // m() interface{ T } + // } + // + n := t.NumMethods() + if n == 0 { + p.print("interface{}") + return + } + + p.print("interface {\n") + p.indent++ + if GcCompatibilityMode { + // print flattened interface + // (useful to compare against gc-generated interfaces) + for i := 0; i < n; i++ { + m := t.Method(i) + p.print(m.Name()) + p.writeSignatureInternal(this, m.Type().(*types.Signature), visited) + p.print("\n") + } + } else { + // print explicit interface methods and embedded types + for i, n := 0, t.NumExplicitMethods(); i < n; i++ { + m := t.ExplicitMethod(i) + p.print(m.Name()) + p.writeSignatureInternal(this, m.Type().(*types.Signature), visited) + p.print("\n") + } + for i, n := 0, t.NumEmbeddeds(); i < n; i++ { + typ := t.Embedded(i) + p.writeTypeInternal(this, typ, visited) + p.print("\n") + } + } + p.indent-- + p.print("}") + + case *types.Map: + p.print("map[") + p.writeTypeInternal(this, t.Key(), visited) + p.print("]") + p.writeTypeInternal(this, t.Elem(), visited) + + case *types.Chan: + var s string + var parens bool + switch t.Dir() { + case types.SendRecv: + s = "chan " + // chan (<-chan T) requires parentheses + if c, _ := t.Elem().(*types.Chan); c != nil && c.Dir() == types.RecvOnly { + parens = true + } + case types.SendOnly: + s = "chan<- " + case types.RecvOnly: + s = "<-chan " + default: + panic("unreachable") + } + p.print(s) + if parens { + p.print("(") + } + p.writeTypeInternal(this, t.Elem(), visited) + if parens { + p.print(")") + } + + case *types.Named: + s := "" + if obj := t.Obj(); obj != nil { + if pkg := obj.Pkg(); pkg != nil { + if pkg != this { + p.print(pkg.Path()) + p.print(".") + } + // TODO(gri): function-local named types should be displayed + // differently from named types at package level to avoid + // ambiguity. + } + s = obj.Name() + } + p.print(s) + + default: + // For externally defined implementations of Type. + p.print(t.String()) + } +} + +func (p *printer) writeTuple(this *types.Package, tup *types.Tuple, variadic bool, visited []types.Type) { + p.print("(") + for i, n := 0, tup.Len(); i < n; i++ { + if i > 0 { + p.print(", ") + } + v := tup.At(i) + if name := v.Name(); name != "" { + p.print(name) + p.print(" ") + } + typ := v.Type() + if variadic && i == n-1 { + p.print("...") + typ = typ.(*types.Slice).Elem() + } + p.writeTypeInternal(this, typ, visited) + } + p.print(")") +} + +func (p *printer) writeSignature(this *types.Package, sig *types.Signature) { + p.writeSignatureInternal(this, sig, make([]types.Type, 8)) +} + +func (p *printer) writeSignatureInternal(this *types.Package, sig *types.Signature, visited []types.Type) { + p.writeTuple(this, sig.Params(), sig.Variadic(), visited) + + res := sig.Results() + n := res.Len() + if n == 0 { + // no result + return + } + + p.print(" ") + if n == 1 && res.At(0).Name() == "" { + // single unnamed result + p.writeTypeInternal(this, res.At(0).Type(), visited) + return + } + + // multiple or named result(s) + p.writeTuple(this, res, false, visited) +}