package types
import (
"bytes"
"fmt"
"go/token"
"sort"
"strconv"
"strings"
"unicode/utf8"
)
type Qualifier func (*Package ) string
func RelativeTo (pkg *Package ) Qualifier {
if pkg == nil {
return nil
}
return func (other *Package ) string {
if pkg == other {
return ""
}
return other .Path ()
}
}
func TypeString (typ Type , qf Qualifier ) string {
var buf bytes .Buffer
WriteType (&buf , typ , qf )
return buf .String ()
}
func WriteType (buf *bytes .Buffer , typ Type , qf Qualifier ) {
newTypeWriter (buf , qf ).typ (typ )
}
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
tparams *TypeParamList
paramNames bool
tpSubscripts bool
pkgInfo bool
}
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 :
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 (';' )
}
pkgAnnotate := false
if w .qf == nil && w .pkgInfo && !token .IsExported (f .name ) {
pkgAnnotate = true
w .pkgInfo = false
}
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 (' ' )
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 :
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 () {
w .string ("any" )
break
}
if t == universeComparable .Type ().(*Named ).underlying {
w .string ("interface{comparable}" )
break
}
}
if t .implicit {
if len (t .methods ) == 0 && len (t .embeddeds ) == 1 {
w .typ (t .embeddeds [0 ])
break
}
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 "
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 w .ctxt != nil {
w .string (strconv .Itoa (w .ctxt .getID (t )))
}
w .typeName (t .obj )
if t .inst != nil {
w .typeList (t .inst .targs .list ())
} else if w .ctxt == nil && t .TypeParams ().Len () != 0 {
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 {
w .string (fmt .Sprintf ("$%d" , i ))
} else {
w .string (t .obj .name )
if w .tpSubscripts || w .ctxt != nil {
w .string (subscript (t .id ))
}
}
default :
w .string (t .String ())
}
}
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 ():
case s .terms .isEmpty ():
w .string (s .terms .String ())
default :
var termHashes []string
for _ , term := range s .terms {
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 {
if tpar == nil {
w .error ("nil type parameter" )
continue
}
if i > 0 {
if tpar .bound != prev {
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 (',' )
}
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 {
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 {
return
}
w .byte (' ' )
if n == 1 && (w .ctxt != nil || sig .results .vars [0 ].name == "" ) {
w .typ (sig .results .vars [0 ].typ )
return
}
w .tuple (sig .results , false )
}
func subscript(x uint64 ) string {
const w = len ("₀" )
var buf [32 * w ]byte
i := len (buf )
for {
i -= w
utf8 .EncodeRune (buf [i :], '₀' +rune (x %10 ))
x /= 10
if x == 0 {
break
}
}
return string (buf [i :])
}
The pages are generated with Golds v0.6.1 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .