format.go raw

   1  // Copyright 2024 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 (error and trace) message formatting support.
   6  
   7  package types
   8  
   9  import (
  10  	"bytes"
  11  	"fmt"
  12  	"go/ast"
  13  	"go/token"
  14  	"strconv"
  15  	"strings"
  16  )
  17  
  18  func sprintf(fset *token.FileSet, qf Qualifier, tpSubscripts bool, format string, args ...any) string {
  19  	for i, arg := range args {
  20  		switch a := arg.(type) {
  21  		case nil:
  22  			arg = "<nil>"
  23  		case operand:
  24  			panic("got operand instead of *operand")
  25  		case *operand:
  26  			arg = operandString(a, qf)
  27  		case token.Pos:
  28  			if fset != nil {
  29  				arg = fset.Position(a).String()
  30  			}
  31  		case ast.Expr:
  32  			arg = ExprString(a)
  33  		case []ast.Expr:
  34  			var buf bytes.Buffer
  35  			buf.WriteByte('[')
  36  			writeExprList(&buf, a)
  37  			buf.WriteByte(']')
  38  			arg = buf.String()
  39  		case Object:
  40  			arg = ObjectString(a, qf)
  41  		case Type:
  42  			var buf bytes.Buffer
  43  			w := newTypeWriter(&buf, qf)
  44  			w.tpSubscripts = tpSubscripts
  45  			w.typ(a)
  46  			arg = buf.String()
  47  		case []Type:
  48  			var buf bytes.Buffer
  49  			w := newTypeWriter(&buf, qf)
  50  			w.tpSubscripts = tpSubscripts
  51  			buf.WriteByte('[')
  52  			for i, x := range a {
  53  				if i > 0 {
  54  					buf.WriteString(", ")
  55  				}
  56  				w.typ(x)
  57  			}
  58  			buf.WriteByte(']')
  59  			arg = buf.String()
  60  		case []*TypeParam:
  61  			var buf bytes.Buffer
  62  			w := newTypeWriter(&buf, qf)
  63  			w.tpSubscripts = tpSubscripts
  64  			buf.WriteByte('[')
  65  			for i, x := range a {
  66  				if i > 0 {
  67  					buf.WriteString(", ")
  68  				}
  69  				w.typ(x)
  70  			}
  71  			buf.WriteByte(']')
  72  			arg = buf.String()
  73  		}
  74  		args[i] = arg
  75  	}
  76  	return fmt.Sprintf(format, args...)
  77  }
  78  
  79  // check may be nil.
  80  func (check *Checker) sprintf(format string, args ...any) string {
  81  	var fset *token.FileSet
  82  	var qf Qualifier
  83  	if check != nil {
  84  		fset = check.fset
  85  		qf = check.qualifier
  86  	}
  87  	return sprintf(fset, qf, false, format, args...)
  88  }
  89  
  90  func (check *Checker) trace(pos token.Pos, format string, args ...any) {
  91  	pos1 := check.fset.Position(pos)
  92  	// Use the width of line and pos values to align the ":" by adding padding before it.
  93  	// Cap padding at 5: 3 digits for the line, 2 digits for the column number, which is
  94  	// ok for most cases.
  95  	w := ndigits(pos1.Line) + ndigits(pos1.Column)
  96  	pad := "     "[:max(5-w, 0)]
  97  	fmt.Printf("%s%s:  %s%s\n",
  98  		pos1,
  99  		pad,
 100  		strings.Repeat(".  ", check.indent),
 101  		sprintf(check.fset, check.qualifier, true, format, args...),
 102  	)
 103  }
 104  
 105  // ndigits returns the number of decimal digits in x.
 106  // For x < 10, the result is always 1.
 107  // For x > 100, the result is always 3.
 108  func ndigits(x int) int {
 109  	switch {
 110  	case x < 10:
 111  		return 1
 112  	case x < 100:
 113  		return 2
 114  	default:
 115  		return 3
 116  	}
 117  }
 118  
 119  // dump is only needed for debugging
 120  func (check *Checker) dump(format string, args ...any) {
 121  	fmt.Println(sprintf(check.fset, check.qualifier, true, format, args...))
 122  }
 123  
 124  func (check *Checker) qualifier(pkg *Package) string {
 125  	// Qualify the package unless it's the package being type-checked.
 126  	if pkg != check.pkg {
 127  		if check.pkgPathMap == nil {
 128  			check.pkgPathMap = make(map[string]map[string]bool)
 129  			check.seenPkgMap = make(map[*Package]bool)
 130  			check.markImports(check.pkg)
 131  		}
 132  		// If the same package name was used by multiple packages, display the full path.
 133  		if len(check.pkgPathMap[pkg.name]) > 1 {
 134  			return strconv.Quote(pkg.path)
 135  		}
 136  		return pkg.name
 137  	}
 138  	return ""
 139  }
 140  
 141  // markImports recursively walks pkg and its imports, to record unique import
 142  // paths in pkgPathMap.
 143  func (check *Checker) markImports(pkg *Package) {
 144  	if check.seenPkgMap[pkg] {
 145  		return
 146  	}
 147  	check.seenPkgMap[pkg] = true
 148  
 149  	forName, ok := check.pkgPathMap[pkg.name]
 150  	if !ok {
 151  		forName = make(map[string]bool)
 152  		check.pkgPathMap[pkg.name] = forName
 153  	}
 154  	forName[pkg.path] = true
 155  
 156  	for _, imp := range pkg.imports {
 157  		check.markImports(imp)
 158  	}
 159  }
 160  
 161  // stripAnnotations removes internal (type) annotations from s.
 162  func stripAnnotations(s string) string {
 163  	var buf strings.Builder
 164  	for _, r := range s {
 165  		// strip #'s and subscript digits
 166  		if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
 167  			buf.WriteRune(r)
 168  		}
 169  	}
 170  	if buf.Len() < len(s) {
 171  		return buf.String()
 172  	}
 173  	return s
 174  }
 175