Source file src/cmd/compile/internal/types2/typestring.go

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file implements printing of types.
     6  
     7  package types2
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"slices"
    13  	"strconv"
    14  	"strings"
    15  	"unicode/utf8"
    16  )
    17  
    18  // A Qualifier controls how named package-level objects are printed in
    19  // calls to [TypeString], [ObjectString], and [SelectionString].
    20  //
    21  // These three formatting routines call the Qualifier for each
    22  // package-level object O, and if the Qualifier returns a non-empty
    23  // string p, the object is printed in the form p.O.
    24  // If it returns an empty string, only the object name O is printed.
    25  //
    26  // Using a nil Qualifier is equivalent to using (*[Package]).Path: the
    27  // object is qualified by the import path, e.g., "encoding/json.Marshal".
    28  type Qualifier func(*Package) string
    29  
    30  // RelativeTo returns a [Qualifier] that fully qualifies members of
    31  // all packages other than pkg.
    32  func RelativeTo(pkg *Package) Qualifier {
    33  	if pkg == nil {
    34  		return nil
    35  	}
    36  	return func(other *Package) string {
    37  		if pkg == other {
    38  			return "" // same package; unqualified
    39  		}
    40  		return other.Path()
    41  	}
    42  }
    43  
    44  // TypeString returns the string representation of typ.
    45  // The [Qualifier] controls the printing of
    46  // package-level objects, and may be nil.
    47  func TypeString(typ Type, qf Qualifier) string {
    48  	var buf bytes.Buffer
    49  	WriteType(&buf, typ, qf)
    50  	return buf.String()
    51  }
    52  
    53  // WriteType writes the string representation of typ to buf.
    54  // The [Qualifier] controls the printing of
    55  // package-level objects, and may be nil.
    56  func WriteType(buf *bytes.Buffer, typ Type, qf Qualifier) {
    57  	newTypeWriter(buf, qf).typ(typ)
    58  }
    59  
    60  // WriteSignature writes the representation of the signature sig to buf,
    61  // without a leading "func" keyword. The [Qualifier] controls the printing
    62  // of package-level objects, and may be nil.
    63  func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) {
    64  	newTypeWriter(buf, qf).signature(sig)
    65  }
    66  
    67  type typeWriter struct {
    68  	buf          *bytes.Buffer
    69  	seen         map[Type]bool
    70  	qf           Qualifier
    71  	ctxt         *Context       // if non-nil, we are type hashing
    72  	tparams      *TypeParamList // local type parameters
    73  	paramNames   bool           // if set, write function parameter names, otherwise, write types only
    74  	tpSubscripts bool           // if set, write type parameter indices as subscripts
    75  	pkgInfo      bool           // package-annotate first unexported-type field to avoid confusing type description
    76  }
    77  
    78  func newTypeWriter(buf *bytes.Buffer, qf Qualifier) *typeWriter {
    79  	return &typeWriter{buf, make(map[Type]bool), qf, nil, nil, true, false, false}
    80  }
    81  
    82  func newTypeHasher(buf *bytes.Buffer, ctxt *Context) *typeWriter {
    83  	assert(ctxt != nil)
    84  	return &typeWriter{buf, make(map[Type]bool), nil, ctxt, nil, false, false, false}
    85  }
    86  
    87  func (w *typeWriter) byte(b byte) {
    88  	if w.ctxt != nil {
    89  		if b == ' ' {
    90  			b = '#'
    91  		}
    92  		w.buf.WriteByte(b)
    93  		return
    94  	}
    95  	w.buf.WriteByte(b)
    96  	if b == ',' || b == ';' {
    97  		w.buf.WriteByte(' ')
    98  	}
    99  }
   100  
   101  func (w *typeWriter) string(s string) {
   102  	w.buf.WriteString(s)
   103  }
   104  
   105  func (w *typeWriter) error(msg string) {
   106  	if w.ctxt != nil {
   107  		panic(msg)
   108  	}
   109  	w.buf.WriteString("<" + msg + ">")
   110  }
   111  
   112  func (w *typeWriter) typ(typ Type) {
   113  	if w.seen[typ] {
   114  		w.error("cycle to " + goTypeName(typ))
   115  		return
   116  	}
   117  	w.seen[typ] = true
   118  	defer delete(w.seen, typ)
   119  
   120  	switch t := typ.(type) {
   121  	case nil:
   122  		w.error("nil")
   123  
   124  	case *Basic:
   125  		// exported basic types go into package unsafe
   126  		// (currently this is just unsafe.Pointer)
   127  		if isExported(t.name) {
   128  			if obj, _ := Unsafe.scope.Lookup(t.name).(*TypeName); obj != nil {
   129  				w.typeName(obj)
   130  				break
   131  			}
   132  		}
   133  		w.string(t.name)
   134  
   135  	case *Array:
   136  		w.byte('[')
   137  		w.string(strconv.FormatInt(t.len, 10))
   138  		w.byte(']')
   139  		w.typ(t.elem)
   140  
   141  	case *Slice:
   142  		w.string("[]")
   143  		w.typ(t.elem)
   144  
   145  	case *Struct:
   146  		w.string("struct{")
   147  		for i, f := range t.fields {
   148  			if i > 0 {
   149  				w.byte(';')
   150  			}
   151  
   152  			// If disambiguating one struct for another, look for the first unexported field.
   153  			// Do this first in case of nested structs; tag the first-outermost field.
   154  			pkgAnnotate := false
   155  			if w.qf == nil && w.pkgInfo && !isExported(f.name) {
   156  				// note for embedded types, type name is field name, and "string" etc are lower case hence unexported.
   157  				pkgAnnotate = true
   158  				w.pkgInfo = false // only tag once
   159  			}
   160  
   161  			// This doesn't do the right thing for embedded type
   162  			// aliases where we should print the alias name, not
   163  			// the aliased type (see go.dev/issue/44410).
   164  			if !f.embedded {
   165  				w.string(f.name)
   166  				w.byte(' ')
   167  			}
   168  			w.typ(f.typ)
   169  			if pkgAnnotate {
   170  				w.string(" /* package ")
   171  				w.string(f.pkg.Path())
   172  				w.string(" */ ")
   173  			}
   174  			if tag := t.Tag(i); tag != "" {
   175  				w.byte(' ')
   176  				// TODO(gri) If tag contains blanks, replacing them with '#'
   177  				//           in Context.TypeHash may produce another tag
   178  				//           accidentally.
   179  				w.string(strconv.Quote(tag))
   180  			}
   181  		}
   182  		w.byte('}')
   183  
   184  	case *Pointer:
   185  		w.byte('*')
   186  		w.typ(t.base)
   187  
   188  	case *Tuple:
   189  		w.tuple(t, false)
   190  
   191  	case *Signature:
   192  		w.string("func")
   193  		w.signature(t)
   194  
   195  	case *Union:
   196  		// Unions only appear as (syntactic) embedded elements
   197  		// in interfaces and syntactically cannot be empty.
   198  		if t.Len() == 0 {
   199  			w.error("empty union")
   200  			break
   201  		}
   202  		for i, t := range t.terms {
   203  			if i > 0 {
   204  				w.string(termSep)
   205  			}
   206  			if t.tilde {
   207  				w.byte('~')
   208  			}
   209  			w.typ(t.typ)
   210  		}
   211  
   212  	case *Interface:
   213  		if w.ctxt == nil {
   214  			if t == asNamed(universeComparable.Type()).underlying {
   215  				w.string("interface{comparable}")
   216  				break
   217  			}
   218  		}
   219  		if t.implicit {
   220  			if len(t.methods) == 0 && len(t.embeddeds) == 1 {
   221  				w.typ(t.embeddeds[0])
   222  				break
   223  			}
   224  			// Something's wrong with the implicit interface.
   225  			// Print it as such and continue.
   226  			w.string("/* implicit */ ")
   227  		}
   228  		w.string("interface{")
   229  		first := true
   230  		if w.ctxt != nil {
   231  			w.typeSet(t.typeSet())
   232  		} else {
   233  			for _, m := range t.methods {
   234  				if !first {
   235  					w.byte(';')
   236  				}
   237  				first = false
   238  				w.string(m.name)
   239  				w.signature(m.typ.(*Signature))
   240  			}
   241  			for _, typ := range t.embeddeds {
   242  				if !first {
   243  					w.byte(';')
   244  				}
   245  				first = false
   246  				w.typ(typ)
   247  			}
   248  		}
   249  		w.byte('}')
   250  
   251  	case *Map:
   252  		w.string("map[")
   253  		w.typ(t.key)
   254  		w.byte(']')
   255  		w.typ(t.elem)
   256  
   257  	case *Chan:
   258  		var s string
   259  		var parens bool
   260  		switch t.dir {
   261  		case SendRecv:
   262  			s = "chan "
   263  			// chan (<-chan T) requires parentheses
   264  			if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly {
   265  				parens = true
   266  			}
   267  		case SendOnly:
   268  			s = "chan<- "
   269  		case RecvOnly:
   270  			s = "<-chan "
   271  		default:
   272  			w.error("unknown channel direction")
   273  		}
   274  		w.string(s)
   275  		if parens {
   276  			w.byte('(')
   277  		}
   278  		w.typ(t.elem)
   279  		if parens {
   280  			w.byte(')')
   281  		}
   282  
   283  	case *Named:
   284  		// If hashing, write a unique prefix for t to represent its identity, since
   285  		// named type identity is pointer identity.
   286  		if w.ctxt != nil {
   287  			w.string(strconv.Itoa(w.ctxt.getID(t)))
   288  		}
   289  		w.typeName(t.obj) // when hashing written for readability of the hash only
   290  		if t.inst != nil {
   291  			// instantiated type
   292  			w.typeList(t.inst.targs.list())
   293  		} else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams
   294  			// parameterized type
   295  			w.tParamList(t.TypeParams().list())
   296  		}
   297  
   298  	case *TypeParam:
   299  		if t.obj == nil {
   300  			w.error("unnamed type parameter")
   301  			break
   302  		}
   303  		if i := slices.Index(w.tparams.list(), t); i >= 0 {
   304  			// The names of type parameters that are declared by the type being
   305  			// hashed are not part of the type identity. Replace them with a
   306  			// placeholder indicating their index.
   307  			w.string(fmt.Sprintf("$%d", i))
   308  		} else {
   309  			w.string(t.obj.name)
   310  			if w.tpSubscripts || w.ctxt != nil {
   311  				w.string(subscript(t.id))
   312  			}
   313  			// If the type parameter name is the same as a predeclared object
   314  			// (say int), point out where it is declared to avoid confusing
   315  			// error messages. This doesn't need to be super-elegant; we just
   316  			// need a clear indication that this is not a predeclared name.
   317  			if w.ctxt == nil && Universe.Lookup(t.obj.name) != nil {
   318  				if isTypes2 {
   319  					w.string(fmt.Sprintf(" /* with %s declared at %v */", t.obj.name, t.obj.Pos()))
   320  				} else {
   321  					// Can't print position information because
   322  					// we don't have a token.FileSet accessible.
   323  					w.string("/* type parameter */")
   324  				}
   325  			}
   326  		}
   327  
   328  	case *Alias:
   329  		w.typeName(t.obj)
   330  		if list := t.targs.list(); len(list) != 0 {
   331  			// instantiated type
   332  			w.typeList(list)
   333  		} else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams
   334  			// parameterized type
   335  			w.tParamList(t.TypeParams().list())
   336  		}
   337  		if w.ctxt != nil {
   338  			// TODO(gri) do we need to print the alias type name, too?
   339  			typ := Unalias(t.obj.typ)
   340  			if typ == nil {
   341  				panic("known implementation limitation: encountered an incomplete alias (see go.dev/issue/78296)")
   342  			}
   343  			w.typ(typ)
   344  		}
   345  
   346  	default:
   347  		// For externally defined implementations of Type.
   348  		// Note: In this case cycles won't be caught.
   349  		w.string(t.String())
   350  	}
   351  }
   352  
   353  // typeSet writes a canonical hash for an interface type set.
   354  func (w *typeWriter) typeSet(s *_TypeSet) {
   355  	assert(w.ctxt != nil)
   356  	first := true
   357  	for _, m := range s.methods {
   358  		if !first {
   359  			w.byte(';')
   360  		}
   361  		first = false
   362  		w.string(m.name)
   363  		w.signature(m.typ.(*Signature))
   364  	}
   365  	switch {
   366  	case s.terms.isAll():
   367  		// nothing to do
   368  	case s.terms.isEmpty():
   369  		w.string(s.terms.String())
   370  	default:
   371  		var termHashes []string
   372  		for _, term := range s.terms {
   373  			// terms are not canonically sorted, so we sort their hashes instead.
   374  			var buf bytes.Buffer
   375  			if term.tilde {
   376  				buf.WriteByte('~')
   377  			}
   378  			newTypeHasher(&buf, w.ctxt).typ(term.typ)
   379  			termHashes = append(termHashes, buf.String())
   380  		}
   381  		slices.Sort(termHashes)
   382  		if !first {
   383  			w.byte(';')
   384  		}
   385  		w.string(strings.Join(termHashes, "|"))
   386  	}
   387  }
   388  
   389  func (w *typeWriter) typeList(list []Type) {
   390  	w.byte('[')
   391  	for i, typ := range list {
   392  		if i > 0 {
   393  			w.byte(',')
   394  		}
   395  		w.typ(typ)
   396  	}
   397  	w.byte(']')
   398  }
   399  
   400  func (w *typeWriter) tParamList(list []*TypeParam) {
   401  	w.byte('[')
   402  	var prev Type
   403  	for i, tpar := range list {
   404  		// Determine the type parameter and its constraint.
   405  		// list is expected to hold type parameter names,
   406  		// but don't crash if that's not the case.
   407  		if tpar == nil {
   408  			w.error("nil type parameter")
   409  			continue
   410  		}
   411  		if i > 0 {
   412  			if tpar.bound != prev {
   413  				// bound changed - write previous one before advancing
   414  				w.byte(' ')
   415  				w.typ(prev)
   416  			}
   417  			w.byte(',')
   418  		}
   419  		prev = tpar.bound
   420  		w.typ(tpar)
   421  	}
   422  	if prev != nil {
   423  		w.byte(' ')
   424  		w.typ(prev)
   425  	}
   426  	w.byte(']')
   427  }
   428  
   429  func (w *typeWriter) typeName(obj *TypeName) {
   430  	w.string(packagePrefix(obj.pkg, w.qf))
   431  	w.string(obj.name)
   432  }
   433  
   434  func (w *typeWriter) tuple(tup *Tuple, variadic bool) {
   435  	w.byte('(')
   436  	if tup != nil {
   437  		for i, v := range tup.vars {
   438  			if i > 0 {
   439  				w.byte(',')
   440  			}
   441  			// parameter names are ignored for type identity and thus type hashes
   442  			if w.ctxt == nil && v.name != "" && w.paramNames {
   443  				w.string(v.name)
   444  				w.byte(' ')
   445  			}
   446  			typ := v.typ
   447  			if variadic && i == len(tup.vars)-1 {
   448  				if slice, ok := typ.(*Slice); ok {
   449  					w.string("...")
   450  					w.typ(slice.elem)
   451  				} else {
   452  					// append(slice, str...) entails various special
   453  					// cases, especially in conjunction with generics.
   454  					// str may be:
   455  					// - a string,
   456  					// - a TypeParam whose typeset includes string, or
   457  					// - a named []byte slice type B resulting from
   458  					//   a client instantiating append([]byte, T) at T=B.
   459  					// For such cases we use the irregular notation
   460  					// func([]byte, T...), with the dots after the type.
   461  					w.typ(typ)
   462  					w.string("...")
   463  				}
   464  			} else {
   465  				w.typ(typ)
   466  			}
   467  		}
   468  	}
   469  	w.byte(')')
   470  }
   471  
   472  func (w *typeWriter) signature(sig *Signature) {
   473  	if sig.TypeParams().Len() != 0 {
   474  		if w.ctxt != nil {
   475  			assert(w.tparams == nil)
   476  			w.tparams = sig.TypeParams()
   477  			defer func() {
   478  				w.tparams = nil
   479  			}()
   480  		}
   481  		w.tParamList(sig.TypeParams().list())
   482  	}
   483  
   484  	w.tuple(sig.params, sig.variadic)
   485  
   486  	n := sig.results.Len()
   487  	if n == 0 {
   488  		// no result
   489  		return
   490  	}
   491  
   492  	w.byte(' ')
   493  	if n == 1 && (w.ctxt != nil || sig.results.vars[0].name == "") {
   494  		// single unnamed result (if type hashing, name must be ignored)
   495  		w.typ(sig.results.vars[0].typ)
   496  		return
   497  	}
   498  
   499  	// multiple or named result(s)
   500  	w.tuple(sig.results, false)
   501  }
   502  
   503  // subscript returns the decimal (utf8) representation of x using subscript digits.
   504  func subscript(x uint64) string {
   505  	const w = len("₀") // all digits 0...9 have the same utf8 width
   506  	var buf [32 * w]byte
   507  	i := len(buf)
   508  	for {
   509  		i -= w
   510  		utf8.EncodeRune(buf[i:], '₀'+rune(x%10)) // '₀' == U+2080
   511  		x /= 10
   512  		if x == 0 {
   513  			break
   514  		}
   515  	}
   516  	return string(buf[i:])
   517  }
   518  

View as plain text