...

Source file src/cmd/compile/internal/types/fmt.go

Documentation: cmd/compile/internal/types

     1  // Copyright 2009 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  package types
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	"cmd/compile/internal/base"
    16  	"cmd/internal/hash"
    17  )
    18  
    19  // BuiltinPkg is a fake package that declares the universe block.
    20  var BuiltinPkg *Pkg
    21  
    22  // LocalPkg is the package being compiled.
    23  var LocalPkg *Pkg
    24  
    25  // UnsafePkg is package unsafe.
    26  var UnsafePkg *Pkg
    27  
    28  // BlankSym is the blank (_) symbol.
    29  var BlankSym *Sym
    30  
    31  // numImport tracks how often a package with a given name is imported.
    32  // It is used to provide a better error message (by using the package
    33  // path to disambiguate) if a package that appears multiple times with
    34  // the same name appears in an error message.
    35  var NumImport = make(map[string]int)
    36  
    37  // fmtMode represents the kind of printing being done.
    38  // The default is regular Go syntax (fmtGo).
    39  // fmtDebug is like fmtGo but for debugging dumps and prints the type kind too.
    40  // fmtTypeID and fmtTypeIDName are for generating various unique representations
    41  // of types used in hashes, the linker, and function/method instantiations.
    42  type fmtMode int
    43  
    44  const (
    45  	fmtGo fmtMode = iota
    46  	fmtDebug
    47  	fmtTypeID
    48  	fmtTypeIDName
    49  )
    50  
    51  // Sym
    52  
    53  // Format implements formatting for a Sym.
    54  // The valid formats are:
    55  //
    56  //	%v	Go syntax: Name for symbols in the local package, PkgName.Name for imported symbols.
    57  //	%+v	Debug syntax: always include PkgName. prefix even for local names.
    58  //	%S	Short syntax: Name only, no matter what.
    59  func (s *Sym) Format(f fmt.State, verb rune) {
    60  	mode := fmtGo
    61  	switch verb {
    62  	case 'v', 'S':
    63  		if verb == 'v' && f.Flag('+') {
    64  			mode = fmtDebug
    65  		}
    66  		fmt.Fprint(f, sconv(s, verb, mode))
    67  
    68  	default:
    69  		fmt.Fprintf(f, "%%!%c(*types.Sym=%p)", verb, s)
    70  	}
    71  }
    72  
    73  func (s *Sym) String() string {
    74  	return sconv(s, 0, fmtGo)
    75  }
    76  
    77  // See #16897 for details about performance implications
    78  // before changing the implementation of sconv.
    79  func sconv(s *Sym, verb rune, mode fmtMode) string {
    80  	if verb == 'L' {
    81  		panic("linksymfmt")
    82  	}
    83  
    84  	if s == nil {
    85  		return "<S>"
    86  	}
    87  
    88  	q := pkgqual(s.Pkg, verb, mode)
    89  	if q == "" {
    90  		return s.Name
    91  	}
    92  
    93  	buf := fmtBufferPool.Get().(*bytes.Buffer)
    94  	buf.Reset()
    95  	defer fmtBufferPool.Put(buf)
    96  
    97  	buf.WriteString(q)
    98  	buf.WriteByte('.')
    99  	buf.WriteString(s.Name)
   100  	return InternString(buf.Bytes())
   101  }
   102  
   103  func sconv2(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) {
   104  	if verb == 'L' {
   105  		panic("linksymfmt")
   106  	}
   107  	if s == nil {
   108  		b.WriteString("<S>")
   109  		return
   110  	}
   111  
   112  	symfmt(b, s, verb, mode)
   113  }
   114  
   115  func symfmt(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) {
   116  	name := s.Name
   117  	if q := pkgqual(s.Pkg, verb, mode); q != "" {
   118  		b.WriteString(q)
   119  		b.WriteByte('.')
   120  	}
   121  	b.WriteString(name)
   122  }
   123  
   124  // pkgqual returns the qualifier that should be used for printing
   125  // symbols from the given package in the given mode.
   126  // If it returns the empty string, no qualification is needed.
   127  func pkgqual(pkg *Pkg, verb rune, mode fmtMode) string {
   128  	if pkg == nil {
   129  		return ""
   130  	}
   131  	if verb != 'S' {
   132  		switch mode {
   133  		case fmtGo: // This is for the user
   134  			if pkg == BuiltinPkg || pkg == LocalPkg {
   135  				return ""
   136  			}
   137  
   138  			// If the name was used by multiple packages, display the full path,
   139  			if pkg.Name != "" && NumImport[pkg.Name] > 1 {
   140  				return strconv.Quote(pkg.Path)
   141  			}
   142  			return pkg.Name
   143  
   144  		case fmtDebug:
   145  			return pkg.Name
   146  
   147  		case fmtTypeIDName:
   148  			// dcommontype, typehash
   149  			return pkg.Name
   150  
   151  		case fmtTypeID:
   152  			// (methodsym), typesym, weaksym
   153  			return pkg.Prefix
   154  		}
   155  	}
   156  
   157  	return ""
   158  }
   159  
   160  // Type
   161  
   162  var BasicTypeNames = []string{
   163  	TINT:        "int",
   164  	TUINT:       "uint",
   165  	TINT8:       "int8",
   166  	TUINT8:      "uint8",
   167  	TINT16:      "int16",
   168  	TUINT16:     "uint16",
   169  	TINT32:      "int32",
   170  	TUINT32:     "uint32",
   171  	TINT64:      "int64",
   172  	TUINT64:     "uint64",
   173  	TUINTPTR:    "uintptr",
   174  	TFLOAT32:    "float32",
   175  	TFLOAT64:    "float64",
   176  	TCOMPLEX64:  "complex64",
   177  	TCOMPLEX128: "complex128",
   178  	TBOOL:       "bool",
   179  	TANY:        "any",
   180  	TSTRING:     "string",
   181  	TNIL:        "nil",
   182  	TIDEAL:      "untyped number",
   183  	TBLANK:      "blank",
   184  }
   185  
   186  var fmtBufferPool = sync.Pool{
   187  	New: func() any {
   188  		return new(bytes.Buffer)
   189  	},
   190  }
   191  
   192  // Format implements formatting for a Type.
   193  // The valid formats are:
   194  //
   195  //	%v	Go syntax
   196  //	%+v	Debug syntax: Go syntax with a KIND- prefix for all but builtins.
   197  //	%L	Go syntax for underlying type if t is named
   198  //	%S	short Go syntax: drop leading "func" in function type
   199  //	%-S	special case for method receiver symbol
   200  func (t *Type) Format(s fmt.State, verb rune) {
   201  	mode := fmtGo
   202  	switch verb {
   203  	case 'v', 'S', 'L':
   204  		if verb == 'v' && s.Flag('+') { // %+v is debug format
   205  			mode = fmtDebug
   206  		}
   207  		if verb == 'S' && s.Flag('-') { // %-S is special case for receiver - short typeid format
   208  			mode = fmtTypeID
   209  		}
   210  		fmt.Fprint(s, tconv(t, verb, mode))
   211  	default:
   212  		fmt.Fprintf(s, "%%!%c(*Type=%p)", verb, t)
   213  	}
   214  }
   215  
   216  // String returns the Go syntax for the type t.
   217  func (t *Type) String() string {
   218  	return tconv(t, 0, fmtGo)
   219  }
   220  
   221  // LinkString returns a string description of t, suitable for use in
   222  // link symbols.
   223  //
   224  // The description corresponds to type identity. That is, for any pair
   225  // of types t1 and t2, Identical(t1, t2) == (t1.LinkString() ==
   226  // t2.LinkString()) is true. Thus it's safe to use as a map key to
   227  // implement a type-identity-keyed map.
   228  func (t *Type) LinkString() string {
   229  	return tconv(t, 0, fmtTypeID)
   230  }
   231  
   232  // NameString generates a user-readable, mostly unique string
   233  // description of t. NameString always returns the same description
   234  // for identical types, even across compilation units.
   235  //
   236  // NameString qualifies identifiers by package name, so it has
   237  // collisions when different packages share the same names and
   238  // identifiers. It also does not distinguish function-scope defined
   239  // types from package-scoped defined types or from each other.
   240  func (t *Type) NameString() string {
   241  	return tconv(t, 0, fmtTypeIDName)
   242  }
   243  
   244  func tconv(t *Type, verb rune, mode fmtMode) string {
   245  	buf := fmtBufferPool.Get().(*bytes.Buffer)
   246  	buf.Reset()
   247  	defer fmtBufferPool.Put(buf)
   248  
   249  	tconv2(buf, t, verb, mode, nil)
   250  	return InternString(buf.Bytes())
   251  }
   252  
   253  // tconv2 writes a string representation of t to b.
   254  // flag and mode control exactly what is printed.
   255  // Any types x that are already in the visited map get printed as @%d where %d=visited[x].
   256  // See #16897 before changing the implementation of tconv.
   257  func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type]int) {
   258  	if off, ok := visited[t]; ok {
   259  		// We've seen this type before, so we're trying to print it recursively.
   260  		// Print a reference to it instead.
   261  		fmt.Fprintf(b, "@%d", off)
   262  		return
   263  	}
   264  	if t == nil {
   265  		b.WriteString("<T>")
   266  		return
   267  	}
   268  	if t.Kind() == TSSA {
   269  		b.WriteString(t.extra.(string))
   270  		return
   271  	}
   272  	if t.Kind() == TTUPLE {
   273  		b.WriteString(t.FieldType(0).String())
   274  		b.WriteByte(',')
   275  		b.WriteString(t.FieldType(1).String())
   276  		return
   277  	}
   278  
   279  	if t.Kind() == TRESULTS {
   280  		tys := t.extra.(*Results).Types
   281  		for i, et := range tys {
   282  			if i > 0 {
   283  				b.WriteByte(',')
   284  			}
   285  			b.WriteString(et.String())
   286  		}
   287  		return
   288  	}
   289  
   290  	if t == AnyType || t == ByteType || t == RuneType {
   291  		// in %-T mode collapse predeclared aliases with their originals.
   292  		switch mode {
   293  		case fmtTypeIDName, fmtTypeID:
   294  			t = Types[t.Kind()]
   295  		default:
   296  			sconv2(b, t.Sym(), 'S', mode)
   297  			return
   298  		}
   299  	}
   300  	if t == ErrorType {
   301  		b.WriteString("error")
   302  		return
   303  	}
   304  
   305  	// Unless the 'L' flag was specified, if the type has a name, just print that name.
   306  	if verb != 'L' && t.Sym() != nil && t != Types[t.Kind()] {
   307  		// Default to 'v' if verb is invalid.
   308  		if verb != 'S' {
   309  			verb = 'v'
   310  		}
   311  
   312  		// In unified IR, function-scope defined types will have a ·N
   313  		// suffix embedded directly in their Name. Trim this off for
   314  		// non-fmtTypeID modes.
   315  		sym := t.Sym()
   316  		if mode != fmtTypeID {
   317  			base, _ := SplitVargenSuffix(sym.Name)
   318  			if len(base) < len(sym.Name) {
   319  				sym = &Sym{Pkg: sym.Pkg, Name: base}
   320  			}
   321  		}
   322  		sconv2(b, sym, verb, mode)
   323  		return
   324  	}
   325  
   326  	if int(t.Kind()) < len(BasicTypeNames) && BasicTypeNames[t.Kind()] != "" {
   327  		var name string
   328  		switch t {
   329  		case UntypedBool:
   330  			name = "untyped bool"
   331  		case UntypedString:
   332  			name = "untyped string"
   333  		case UntypedInt:
   334  			name = "untyped int"
   335  		case UntypedRune:
   336  			name = "untyped rune"
   337  		case UntypedFloat:
   338  			name = "untyped float"
   339  		case UntypedComplex:
   340  			name = "untyped complex"
   341  		default:
   342  			name = BasicTypeNames[t.Kind()]
   343  		}
   344  		b.WriteString(name)
   345  		return
   346  	}
   347  
   348  	if mode == fmtDebug {
   349  		b.WriteString(t.Kind().String())
   350  		b.WriteByte('-')
   351  		tconv2(b, t, 'v', fmtGo, visited)
   352  		return
   353  	}
   354  
   355  	// At this point, we might call tconv2 recursively. Add the current type to the visited list so we don't
   356  	// try to print it recursively.
   357  	// We record the offset in the result buffer where the type's text starts. This offset serves as a reference
   358  	// point for any later references to the same type.
   359  	// Note that we remove the type from the visited map as soon as the recursive call is done.
   360  	// This prevents encoding types like map[*int]*int as map[*int]@4. (That encoding would work,
   361  	// but I'd like to use the @ notation only when strictly necessary.)
   362  	if visited == nil {
   363  		visited = map[*Type]int{}
   364  	}
   365  	visited[t] = b.Len()
   366  	defer delete(visited, t)
   367  
   368  	switch t.Kind() {
   369  	case TPTR:
   370  		b.WriteByte('*')
   371  		switch mode {
   372  		case fmtTypeID, fmtTypeIDName:
   373  			if verb == 'S' {
   374  				tconv2(b, t.Elem(), 'S', mode, visited)
   375  				return
   376  			}
   377  		}
   378  		tconv2(b, t.Elem(), 'v', mode, visited)
   379  
   380  	case TARRAY:
   381  		b.WriteByte('[')
   382  		b.WriteString(strconv.FormatInt(t.NumElem(), 10))
   383  		b.WriteByte(']')
   384  		tconv2(b, t.Elem(), 0, mode, visited)
   385  
   386  	case TSLICE:
   387  		b.WriteString("[]")
   388  		tconv2(b, t.Elem(), 0, mode, visited)
   389  
   390  	case TCHAN:
   391  		switch t.ChanDir() {
   392  		case Crecv:
   393  			b.WriteString("<-chan ")
   394  			tconv2(b, t.Elem(), 0, mode, visited)
   395  		case Csend:
   396  			b.WriteString("chan<- ")
   397  			tconv2(b, t.Elem(), 0, mode, visited)
   398  		default:
   399  			b.WriteString("chan ")
   400  			if t.Elem() != nil && t.Elem().IsChan() && t.Elem().Sym() == nil && t.Elem().ChanDir() == Crecv {
   401  				b.WriteByte('(')
   402  				tconv2(b, t.Elem(), 0, mode, visited)
   403  				b.WriteByte(')')
   404  			} else {
   405  				tconv2(b, t.Elem(), 0, mode, visited)
   406  			}
   407  		}
   408  
   409  	case TMAP:
   410  		b.WriteString("map[")
   411  		tconv2(b, t.Key(), 0, mode, visited)
   412  		b.WriteByte(']')
   413  		tconv2(b, t.Elem(), 0, mode, visited)
   414  
   415  	case TINTER:
   416  		if t.IsEmptyInterface() {
   417  			b.WriteString("interface {}")
   418  			break
   419  		}
   420  		b.WriteString("interface {")
   421  		for i, f := range t.AllMethods() {
   422  			if i != 0 {
   423  				b.WriteByte(';')
   424  			}
   425  			b.WriteByte(' ')
   426  			switch {
   427  			case f.Sym == nil:
   428  				// Check first that a symbol is defined for this type.
   429  				// Wrong interface definitions may have types lacking a symbol.
   430  				break
   431  			case IsExported(f.Sym.Name):
   432  				sconv2(b, f.Sym, 'S', mode)
   433  			default:
   434  				smode := mode
   435  				if mode != fmtTypeIDName {
   436  					smode = fmtTypeID
   437  				}
   438  				sconv2(b, f.Sym, 'v', smode)
   439  			}
   440  			tconv2(b, f.Type, 'S', mode, visited)
   441  		}
   442  		if len(t.AllMethods()) != 0 {
   443  			b.WriteByte(' ')
   444  		}
   445  		b.WriteByte('}')
   446  
   447  	case TFUNC:
   448  		if verb == 'S' {
   449  			// no leading func
   450  		} else {
   451  			if t.Recv() != nil {
   452  				b.WriteString("method")
   453  				formatParams(b, t.Recvs(), mode, visited)
   454  				b.WriteByte(' ')
   455  			}
   456  			b.WriteString("func")
   457  		}
   458  		formatParams(b, t.Params(), mode, visited)
   459  
   460  		switch t.NumResults() {
   461  		case 0:
   462  			// nothing to do
   463  
   464  		case 1:
   465  			b.WriteByte(' ')
   466  			tconv2(b, t.Result(0).Type, 0, mode, visited) // struct->field->field's type
   467  
   468  		default:
   469  			b.WriteByte(' ')
   470  			formatParams(b, t.Results(), mode, visited)
   471  		}
   472  
   473  	case TSTRUCT:
   474  		if m := t.StructType().Map; m != nil {
   475  			mt := m.MapType()
   476  			// Format the bucket struct for map[x]y as map.group[x]y.
   477  			// This avoids a recursive print that generates very long names.
   478  			switch t {
   479  			case mt.Group:
   480  				b.WriteString("map.group[")
   481  			default:
   482  				base.Fatalf("unknown internal map type")
   483  			}
   484  			tconv2(b, m.Key(), 0, mode, visited)
   485  			b.WriteByte(']')
   486  			tconv2(b, m.Elem(), 0, mode, visited)
   487  			break
   488  		}
   489  
   490  		b.WriteString("struct {")
   491  		for i, f := range t.Fields() {
   492  			if i != 0 {
   493  				b.WriteByte(';')
   494  			}
   495  			b.WriteByte(' ')
   496  			fldconv(b, f, 'L', mode, visited, false)
   497  		}
   498  		if t.NumFields() != 0 {
   499  			b.WriteByte(' ')
   500  		}
   501  		b.WriteByte('}')
   502  
   503  	case TFORW:
   504  		b.WriteString("undefined")
   505  		if t.Sym() != nil {
   506  			b.WriteByte(' ')
   507  			sconv2(b, t.Sym(), 'v', mode)
   508  		}
   509  
   510  	case TUNSAFEPTR:
   511  		b.WriteString("unsafe.Pointer")
   512  
   513  	case Txxx:
   514  		b.WriteString("Txxx")
   515  
   516  	default:
   517  		// Don't know how to handle - fall back to detailed prints
   518  		b.WriteString(t.Kind().String())
   519  		b.WriteString(" <")
   520  		sconv2(b, t.Sym(), 'v', mode)
   521  		b.WriteString(">")
   522  
   523  	}
   524  }
   525  
   526  func formatParams(b *bytes.Buffer, params []*Field, mode fmtMode, visited map[*Type]int) {
   527  	b.WriteByte('(')
   528  	fieldVerb := 'v'
   529  	switch mode {
   530  	case fmtTypeID, fmtTypeIDName, fmtGo:
   531  		// no argument names on function signature, and no "noescape"/"nosplit" tags
   532  		fieldVerb = 'S'
   533  	}
   534  	for i, param := range params {
   535  		if i != 0 {
   536  			b.WriteString(", ")
   537  		}
   538  		fldconv(b, param, fieldVerb, mode, visited, true)
   539  	}
   540  	b.WriteByte(')')
   541  }
   542  
   543  func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Type]int, isParam bool) {
   544  	if f == nil {
   545  		b.WriteString("<T>")
   546  		return
   547  	}
   548  
   549  	var name string
   550  	nameSep := " "
   551  	if verb != 'S' {
   552  		s := f.Sym
   553  
   554  		// Using type aliases and embedded fields, it's possible to
   555  		// construct types that can't be directly represented as a
   556  		// type literal. For example, given "type Int = int" (#50190),
   557  		// it would be incorrect to format "struct{ Int }" as either
   558  		// "struct{ int }" or "struct{ Int int }", because those each
   559  		// represent other, distinct types.
   560  		//
   561  		// So for the purpose of LinkString (i.e., fmtTypeID), we use
   562  		// the non-standard syntax "struct{ Int = int }" to represent
   563  		// embedded fields that have been renamed through the use of
   564  		// type aliases.
   565  		if f.Embedded != 0 {
   566  			if mode == fmtTypeID {
   567  				nameSep = " = "
   568  
   569  				// Compute tsym, the symbol that would normally be used as
   570  				// the field name when embedding f.Type.
   571  				// TODO(mdempsky): Check for other occurrences of this logic
   572  				// and deduplicate.
   573  				typ := f.Type
   574  				if typ.IsPtr() {
   575  					base.Assertf(typ.Sym() == nil, "embedded pointer type has name: %L", typ)
   576  					typ = typ.Elem()
   577  				}
   578  				tsym := typ.Sym()
   579  
   580  				// If the field name matches the embedded type's name, then
   581  				// suppress printing of the field name. For example, format
   582  				// "struct{ T }" as simply that instead of "struct{ T = T }".
   583  				if tsym != nil && (s == tsym || IsExported(tsym.Name) && s.Name == tsym.Name) {
   584  					s = nil
   585  				}
   586  			} else {
   587  				// Suppress the field name for embedded fields for
   588  				// non-LinkString formats, to match historical behavior.
   589  				// TODO(mdempsky): Re-evaluate this.
   590  				s = nil
   591  			}
   592  		}
   593  
   594  		if s != nil {
   595  			if isParam {
   596  				name = fmt.Sprint(f.Nname)
   597  			} else if verb == 'L' {
   598  				name = s.Name
   599  				if !IsExported(name) && mode != fmtTypeIDName {
   600  					name = sconv(s, 0, mode) // qualify non-exported names (used on structs, not on funarg)
   601  				}
   602  			} else {
   603  				name = sconv(s, 0, mode)
   604  			}
   605  		}
   606  	}
   607  
   608  	if name != "" {
   609  		b.WriteString(name)
   610  		b.WriteString(nameSep)
   611  	}
   612  
   613  	if f.IsDDD() {
   614  		var et *Type
   615  		if f.Type != nil {
   616  			et = f.Type.Elem()
   617  		}
   618  		b.WriteString("...")
   619  		tconv2(b, et, 0, mode, visited)
   620  	} else {
   621  		tconv2(b, f.Type, 0, mode, visited)
   622  	}
   623  
   624  	if verb != 'S' && !isParam && f.Note != "" {
   625  		b.WriteString(" ")
   626  		b.WriteString(strconv.Quote(f.Note))
   627  	}
   628  }
   629  
   630  // SplitVargenSuffix returns name split into a base string and a ·N
   631  // suffix, if any.
   632  func SplitVargenSuffix(name string) (base, suffix string) {
   633  	i := len(name)
   634  	for i > 0 && name[i-1] >= '0' && name[i-1] <= '9' {
   635  		i--
   636  	}
   637  	const dot = "·"
   638  	if i >= len(dot) && name[i-len(dot):i] == dot {
   639  		i -= len(dot)
   640  		return name[:i], name[i:]
   641  	}
   642  	return name, ""
   643  }
   644  
   645  // SplitMethSuffix returns name split into a defining type name and a .m
   646  // suffix, if any.
   647  func SplitMethSuffix(name string) (tname, suffix string) {
   648  	i := strings.LastIndex(name, ".")
   649  	if i >= 0 {
   650  		return name[:i], name[i:]
   651  	}
   652  	return name, ""
   653  }
   654  
   655  // TypeHash computes a hash value for type t to use in type switch statements.
   656  func TypeHash(t *Type) uint32 {
   657  	p := t.LinkString()
   658  
   659  	// Using a cryptographic hash is overkill but minimizes accidental collisions.
   660  	h := hash.Sum32([]byte(p))
   661  	return binary.LittleEndian.Uint32(h[:4])
   662  }
   663  

View as plain text