// 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" "sort" "strconv" "strings" "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() } } // 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) { newTypeWriter(buf, qf).typ(typ) } // 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) { newTypeWriter(buf, qf).signature(sig) } type typeWriter struct { buf *bytes.Buffer seen map[Type]bool qf Qualifier ctxt *Context // if non-nil, we are type hashing tparams *TypeParamList // local type parameters paramNames bool // if set, write function parameter names, otherwise, write types only tpSubscripts bool // if set, write type parameter indices as subscripts pkgInfo bool // package-annotate first unexported-type field to avoid confusing type description } func newTypeWriter(buf *bytes.Buffer, qf Qualifier) *typeWriter { return &typeWriter{buf, make(map[Type]bool), qf, nil, nil, true, false, false} } func newTypeHasher(buf *bytes.Buffer, ctxt *Context) *typeWriter { assert(ctxt != nil) return &typeWriter{buf, make(map[Type]bool), nil, ctxt, nil, false, false, false} } func (w *typeWriter) byte(b byte) { if w.ctxt != nil { if b == ' ' { b = '#' } w.buf.WriteByte(b) return } w.buf.WriteByte(b) if b == ',' || b == ';' { w.buf.WriteByte(' ') } } func (w *typeWriter) string(s string) { w.buf.WriteString(s) } func (w *typeWriter) error(msg string) { if w.ctxt != nil { panic(msg) } w.buf.WriteString("<" + msg + ">") } func (w *typeWriter) typ(typ Type) { if w.seen[typ] { w.error("cycle to " + goTypeName(typ)) return } w.seen[typ] = true defer delete(w.seen, typ) switch t := typ.(type) { case nil: w.error("nil") 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 { w.typeName(obj) break } } w.string(t.name) case *Array: w.byte('[') w.string(strconv.FormatInt(t.len, 10)) w.byte(']') w.typ(t.elem) case *Slice: w.string("[]") w.typ(t.elem) case *Struct: w.string("struct{") for i, f := range t.fields { if i > 0 { w.byte(';') } // If disambiguating one struct for another, look for the first unexported field. // Do this first in case of nested structs; tag the first-outermost field. pkgAnnotate := false if w.qf == nil && w.pkgInfo && !token.IsExported(f.name) { // note for embedded types, type name is field name, and "string" etc are lower case hence unexported. pkgAnnotate = true w.pkgInfo = false // only tag once } // This doesn't do the right thing for embedded type // aliases where we should print the alias name, not // the aliased type (see go.dev/issue/44410). if !f.embedded { w.string(f.name) w.byte(' ') } w.typ(f.typ) if pkgAnnotate { w.string(" /* package ") w.string(f.pkg.Path()) w.string(" */ ") } if tag := t.Tag(i); tag != "" { w.byte(' ') // TODO(rfindley) If tag contains blanks, replacing them with '#' // in Context.TypeHash may produce another tag // accidentally. w.string(strconv.Quote(tag)) } } w.byte('}') case *Pointer: w.byte('*') w.typ(t.base) case *Tuple: w.tuple(t, false) case *Signature: w.string("func") w.signature(t) case *Union: // Unions only appear as (syntactic) embedded elements // in interfaces and syntactically cannot be empty. if t.Len() == 0 { w.error("empty union") break } for i, t := range t.terms { if i > 0 { w.string(termSep) } if t.tilde { w.byte('~') } w.typ(t.typ) } case *Interface: if w.ctxt == nil { if t == universeAny.Type() { // When not hashing, we can try to improve type strings by writing "any" // for a type that is pointer-identical to universeAny. This logic should // be deprecated by more robust handling for aliases. w.string("any") break } if t == asNamed(universeComparable.Type()).underlying { w.string("interface{comparable}") break } } if t.implicit { if len(t.methods) == 0 && len(t.embeddeds) == 1 { w.typ(t.embeddeds[0]) break } // Something's wrong with the implicit interface. // Print it as such and continue. w.string("/* implicit */ ") } w.string("interface{") first := true if w.ctxt != nil { w.typeSet(t.typeSet()) } else { for _, m := range t.methods { if !first { w.byte(';') } first = false w.string(m.name) w.signature(m.typ.(*Signature)) } for _, typ := range t.embeddeds { if !first { w.byte(';') } first = false w.typ(typ) } } w.byte('}') case *Map: w.string("map[") w.typ(t.key) w.byte(']') w.typ(t.elem) 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: w.error("unknown channel direction") } w.string(s) if parens { w.byte('(') } w.typ(t.elem) if parens { w.byte(')') } case *Named: // If hashing, write a unique prefix for t to represent its identity, since // named type identity is pointer identity. if w.ctxt != nil { w.string(strconv.Itoa(w.ctxt.getID(t))) } w.typeName(t.obj) // when hashing written for readability of the hash only if t.inst != nil { // instantiated type w.typeList(t.inst.targs.list()) } else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams // parameterized type w.tParamList(t.TypeParams().list()) } case *TypeParam: if t.obj == nil { w.error("unnamed type parameter") break } if i := tparamIndex(w.tparams.list(), t); i >= 0 { // The names of type parameters that are declared by the type being // hashed are not part of the type identity. Replace them with a // placeholder indicating their index. w.string(fmt.Sprintf("$%d", i)) } else { w.string(t.obj.name) if w.tpSubscripts || w.ctxt != nil { w.string(subscript(t.id)) } // If the type parameter name is the same as a predeclared object // (say int), point out where it is declared to avoid confusing // error messages. This doesn't need to be super-elegant; we just // need a clear indication that this is not a predeclared name. // Note: types2 prints position information here - we can't do // that because we don't have a token.FileSet accessible. if w.ctxt == nil && Universe.Lookup(t.obj.name) != nil { w.string("/* type parameter */") } } case *Alias: w.typeName(t.obj) if w.ctxt != nil { // TODO(gri) do we need to print the alias type name, too? w.typ(Unalias(t.obj.typ)) } default: // For externally defined implementations of Type. // Note: In this case cycles won't be caught. w.string(t.String()) } } // typeSet writes a canonical hash for an interface type set. func (w *typeWriter) typeSet(s *_TypeSet) { assert(w.ctxt != nil) first := true for _, m := range s.methods { if !first { w.byte(';') } first = false w.string(m.name) w.signature(m.typ.(*Signature)) } switch { case s.terms.isAll(): // nothing to do case s.terms.isEmpty(): w.string(s.terms.String()) default: var termHashes []string for _, term := range s.terms { // terms are not canonically sorted, so we sort their hashes instead. var buf bytes.Buffer if term.tilde { buf.WriteByte('~') } newTypeHasher(&buf, w.ctxt).typ(term.typ) termHashes = append(termHashes, buf.String()) } sort.Strings(termHashes) if !first { w.byte(';') } w.string(strings.Join(termHashes, "|")) } } func (w *typeWriter) typeList(list []Type) { w.byte('[') for i, typ := range list { if i > 0 { w.byte(',') } w.typ(typ) } w.byte(']') } func (w *typeWriter) tParamList(list []*TypeParam) { w.byte('[') var prev Type for i, tpar := range list { // Determine the type parameter and its constraint. // list is expected to hold type parameter names, // but don't crash if that's not the case. if tpar == nil { w.error("nil type parameter") continue } if i > 0 { if tpar.bound != prev { // bound changed - write previous one before advancing w.byte(' ') w.typ(prev) } w.byte(',') } prev = tpar.bound w.typ(tpar) } if prev != nil { w.byte(' ') w.typ(prev) } w.byte(']') } func (w *typeWriter) typeName(obj *TypeName) { w.string(packagePrefix(obj.pkg, w.qf)) w.string(obj.name) } func (w *typeWriter) tuple(tup *Tuple, variadic bool) { w.byte('(') if tup != nil { for i, v := range tup.vars { if i > 0 { w.byte(',') } // parameter names are ignored for type identity and thus type hashes if w.ctxt == nil && v.name != "" && w.paramNames { w.string(v.name) w.byte(' ') } typ := v.typ if variadic && i == len(tup.vars)-1 { if s, ok := typ.(*Slice); ok { w.string("...") typ = s.elem } else { // special case: // append(s, "foo"...) leads to signature func([]byte, string...) if t, _ := under(typ).(*Basic); t == nil || t.kind != String { w.error("expected string type") continue } w.typ(typ) w.string("...") continue } } w.typ(typ) } } w.byte(')') } func (w *typeWriter) signature(sig *Signature) { if sig.TypeParams().Len() != 0 { if w.ctxt != nil { assert(w.tparams == nil) w.tparams = sig.TypeParams() defer func() { w.tparams = nil }() } w.tParamList(sig.TypeParams().list()) } w.tuple(sig.params, sig.variadic) n := sig.results.Len() if n == 0 { // no result return } w.byte(' ') if n == 1 && (w.ctxt != nil || sig.results.vars[0].name == "") { // single unnamed result (if type hashing, name must be ignored) w.typ(sig.results.vars[0].typ) return } // multiple or named result(s) w.tuple(sig.results, false) } // 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:]) }