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