// 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 file implements printing of types. package types import ( "bytes" "fmt" "go/token" "unicode/utf8" ) // A Qualifier controls how named package-level objects are printed in // calls to TypeString, ObjectString, and SelectionString. // // These three formatting routines call the Qualifier for each // package-level object O, and if the Qualifier returns a non-empty // string p, the object is printed in the form p.O. // If it returns an empty string, only the object name O is printed. // // Using a nil Qualifier is equivalent to using (*Package).Path: the // object is qualified by the import path, e.g., "encoding/json.Marshal". // type Qualifier func(*Package) string // RelativeTo returns a Qualifier that fully qualifies members of // all packages other than pkg. func RelativeTo(pkg *Package) Qualifier { if pkg == nil { return nil } return func(other *Package) string { if pkg == other { return "" // same package; unqualified } return other.Path() } } // If gcCompatibilityMode is set, printing of types is modified // to match the representation of some types in the gc compiler: // // - byte and rune lose their alias name and simply stand for // uint8 and int32 respectively // - embedded interfaces get flattened (the embedding info is lost, // and certain recursive interface types cannot be printed anymore) // // This makes it easier to compare packages computed with the type- // checker vs packages imported from gc export data. // // Caution: This flag affects all uses of WriteType, globally. // It is only provided for testing in conjunction with // gc-generated data. // // This flag is exported in the x/tools/go/types package. We don't // need it at the moment in the std repo and so we don't export it // anymore. We should eventually try to remove it altogether. // TODO(gri) remove this var gcCompatibilityMode bool // TypeString returns the string representation of typ. // The Qualifier controls the printing of // package-level objects, and may be nil. func TypeString(typ Type, qf Qualifier) string { var buf bytes.Buffer WriteType(&buf, typ, qf) return buf.String() } // WriteType writes the string representation of typ to buf. // The Qualifier controls the printing of // package-level objects, and may be nil. func WriteType(buf *bytes.Buffer, typ Type, qf Qualifier) { writeType(buf, typ, qf, make([]Type, 0, 8)) } // instanceMarker is the prefix for an instantiated type // in "non-evaluated" instance form. const instanceMarker = '#' func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []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 { fmt.Fprintf(buf, "○%T", goTypeName(typ)) // cycle to typ return } } visited = append(visited, typ) switch t := typ.(type) { case nil: buf.WriteString("") case *Basic: // exported basic types go into package unsafe // (currently this is just unsafe.Pointer) if token.IsExported(t.name) { if obj, _ := Unsafe.scope.Lookup(t.name).(*TypeName); obj != nil { writeTypeName(buf, obj, qf) break } } if gcCompatibilityMode { // forget the alias names switch t.kind { case Byte: t = Typ[Uint8] case Rune: t = Typ[Int32] } } buf.WriteString(t.name) case *Array: fmt.Fprintf(buf, "[%d]", t.len) writeType(buf, t.elem, qf, visited) case *Slice: buf.WriteString("[]") writeType(buf, t.elem, qf, visited) case *Struct: buf.WriteString("struct{") for i, f := range t.fields { if i > 0 { buf.WriteString("; ") } // This doesn't do the right thing for embedded type // aliases where we should print the alias name, not // the aliased type (see issue #44410). if !f.embedded { buf.WriteString(f.name) buf.WriteByte(' ') } writeType(buf, f.typ, qf, visited) if tag := t.Tag(i); tag != "" { fmt.Fprintf(buf, " %q", tag) } } buf.WriteByte('}') case *Pointer: buf.WriteByte('*') writeType(buf, t.base, qf, visited) case *Tuple: writeTuple(buf, t, false, qf, visited) case *Signature: buf.WriteString("func") writeSignature(buf, t, qf, visited) case *_Sum: for i, t := range t.types { if i > 0 { buf.WriteString(", ") } writeType(buf, t, qf, visited) } case *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 embedded // interface types that (directly or indirectly) embed the // current interface. For instance, consider the result type // of m: // // type T interface{ // m() interface{ T } // } // buf.WriteString("interface{") empty := true if gcCompatibilityMode { // print flattened interface // (useful to compare against gc-generated interfaces) for i, m := range t.allMethods { if i > 0 { buf.WriteString("; ") } buf.WriteString(m.name) writeSignature(buf, m.typ.(*Signature), qf, visited) empty = false } if !empty && t.allTypes != nil { buf.WriteString("; ") } if t.allTypes != nil { buf.WriteString("type ") writeType(buf, t.allTypes, qf, visited) } } else { // print explicit interface methods and embedded types for i, m := range t.methods { if i > 0 { buf.WriteString("; ") } buf.WriteString(m.name) writeSignature(buf, m.typ.(*Signature), qf, visited) empty = false } if !empty && t.types != nil { buf.WriteString("; ") } if t.types != nil { buf.WriteString("type ") writeType(buf, t.types, qf, visited) empty = false } if !empty && len(t.embeddeds) > 0 { buf.WriteString("; ") } for i, typ := range t.embeddeds { if i > 0 { buf.WriteString("; ") } writeType(buf, typ, qf, visited) empty = false } } if t.allMethods == nil || len(t.methods) > len(t.allMethods) { if !empty { buf.WriteByte(' ') } buf.WriteString("/* incomplete */") } buf.WriteByte('}') case *Map: buf.WriteString("map[") writeType(buf, t.key, qf, visited) buf.WriteByte(']') writeType(buf, t.elem, qf, visited) case *Chan: var s string var parens bool switch t.dir { case SendRecv: s = "chan " // chan (<-chan T) requires parentheses if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly { parens = true } case SendOnly: s = "chan<- " case RecvOnly: s = "<-chan " default: panic("unreachable") } buf.WriteString(s) if parens { buf.WriteByte('(') } writeType(buf, t.elem, qf, visited) if parens { buf.WriteByte(')') } case *Named: writeTypeName(buf, t.obj, qf) if t.targs != nil { // instantiated type buf.WriteByte('[') writeTypeList(buf, t.targs, qf, visited) buf.WriteByte(']') } else if t.tparams != nil { // parameterized type writeTParamList(buf, t.tparams, qf, visited) } case *_TypeParam: s := "?" if t.obj != nil { s = t.obj.name } buf.WriteString(s + subscript(t.id)) case *instance: buf.WriteByte(instanceMarker) // indicate "non-evaluated" syntactic instance writeTypeName(buf, t.base.obj, qf) buf.WriteByte('[') writeTypeList(buf, t.targs, qf, visited) buf.WriteByte(']') case *bottom: buf.WriteString("⊥") case *top: buf.WriteString("⊤") default: // For externally defined implementations of Type. buf.WriteString(t.String()) } } func writeTypeList(buf *bytes.Buffer, list []Type, qf Qualifier, visited []Type) { for i, typ := range list { if i > 0 { buf.WriteString(", ") } writeType(buf, typ, qf, visited) } } func writeTParamList(buf *bytes.Buffer, list []*TypeName, qf Qualifier, visited []Type) { // TODO(rFindley) compare this with the corresponding implementation in types2 buf.WriteString("[") var prev Type for i, p := range list { // TODO(rFindley) support 'any' sugar here. var b Type = &emptyInterface if t, _ := p.typ.(*_TypeParam); t != nil && t.bound != nil { b = t.bound } if i > 0 { if b != prev { // type bound changed - write previous one before advancing buf.WriteByte(' ') writeType(buf, prev, qf, visited) } buf.WriteString(", ") } prev = b if t, _ := p.typ.(*_TypeParam); t != nil { writeType(buf, t, qf, visited) } else { buf.WriteString(p.name) } } if prev != nil { buf.WriteByte(' ') writeType(buf, prev, qf, visited) } buf.WriteByte(']') } func writeTypeName(buf *bytes.Buffer, obj *TypeName, qf Qualifier) { s := "" if obj != nil { if obj.pkg != nil { writePackage(buf, obj.pkg, qf) } // TODO(gri): function-local named types should be displayed // differently from named types at package level to avoid // ambiguity. s = obj.name } buf.WriteString(s) } func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visited []Type) { buf.WriteByte('(') if tup != nil { for i, v := range tup.vars { if i > 0 { buf.WriteString(", ") } if v.name != "" { buf.WriteString(v.name) buf.WriteByte(' ') } typ := v.typ if variadic && i == len(tup.vars)-1 { if s, ok := typ.(*Slice); ok { buf.WriteString("...") typ = s.elem } else { // special case: // append(s, "foo"...) leads to signature func([]byte, string...) if t := asBasic(typ); t == nil || t.kind != String { panic("internal error: string type expected") } writeType(buf, typ, qf, visited) buf.WriteString("...") continue } } writeType(buf, typ, qf, visited) } } buf.WriteByte(')') } // WriteSignature writes the representation of the signature sig to buf, // without a leading "func" keyword. // The Qualifier controls the printing of // package-level objects, and may be nil. func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) { writeSignature(buf, sig, qf, make([]Type, 0, 8)) } func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) { if sig.tparams != nil { writeTParamList(buf, sig.tparams, qf, visited) } writeTuple(buf, sig.params, sig.variadic, qf, visited) n := sig.results.Len() if n == 0 { // no result return } buf.WriteByte(' ') if n == 1 && sig.results.vars[0].name == "" { // single unnamed result writeType(buf, sig.results.vars[0].typ, qf, visited) return } // multiple or named result(s) writeTuple(buf, sig.results, false, qf, visited) } // subscript returns the decimal (utf8) representation of x using subscript digits. func subscript(x uint64) string { const w = len("₀") // all digits 0...9 have the same utf8 width var buf [32 * w]byte i := len(buf) for { i -= w utf8.EncodeRune(buf[i:], '₀'+rune(x%10)) // '₀' == U+2080 x /= 10 if x == 0 { break } } return string(buf[i:]) }