Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/google/go-cmp/cmp/report_reflect.go
2880 views
1
// Copyright 2019, 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 cmp
6
7
import (
8
"bytes"
9
"fmt"
10
"reflect"
11
"strconv"
12
"strings"
13
"unicode"
14
"unicode/utf8"
15
16
"github.com/google/go-cmp/cmp/internal/value"
17
)
18
19
var (
20
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
21
stringType = reflect.TypeOf((*string)(nil)).Elem()
22
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
23
byteType = reflect.TypeOf((*byte)(nil)).Elem()
24
)
25
26
type formatValueOptions struct {
27
// AvoidStringer controls whether to avoid calling custom stringer
28
// methods like error.Error or fmt.Stringer.String.
29
AvoidStringer bool
30
31
// PrintAddresses controls whether to print the address of all pointers,
32
// slice elements, and maps.
33
PrintAddresses bool
34
35
// QualifiedNames controls whether FormatType uses the fully qualified name
36
// (including the full package path as opposed to just the package name).
37
QualifiedNames bool
38
39
// VerbosityLevel controls the amount of output to produce.
40
// A higher value produces more output. A value of zero or lower produces
41
// no output (represented using an ellipsis).
42
// If LimitVerbosity is false, then the level is treated as infinite.
43
VerbosityLevel int
44
45
// LimitVerbosity specifies that formatting should respect VerbosityLevel.
46
LimitVerbosity bool
47
}
48
49
// FormatType prints the type as if it were wrapping s.
50
// This may return s as-is depending on the current type and TypeMode mode.
51
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
52
// Check whether to emit the type or not.
53
switch opts.TypeMode {
54
case autoType:
55
switch t.Kind() {
56
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
57
if s.Equal(textNil) {
58
return s
59
}
60
default:
61
return s
62
}
63
if opts.DiffMode == diffIdentical {
64
return s // elide type for identical nodes
65
}
66
case elideType:
67
return s
68
}
69
70
// Determine the type label, applying special handling for unnamed types.
71
typeName := value.TypeString(t, opts.QualifiedNames)
72
if t.Name() == "" {
73
// According to Go grammar, certain type literals contain symbols that
74
// do not strongly bind to the next lexicographical token (e.g., *T).
75
switch t.Kind() {
76
case reflect.Chan, reflect.Func, reflect.Ptr:
77
typeName = "(" + typeName + ")"
78
}
79
}
80
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
81
}
82
83
// wrapParens wraps s with a set of parenthesis, but avoids it if the
84
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
85
// It handles unwrapping one level of pointer-reference nodes.
86
func wrapParens(s textNode) textNode {
87
var refNode *textWrap
88
if s2, ok := s.(*textWrap); ok {
89
// Unwrap a single pointer reference node.
90
switch s2.Metadata.(type) {
91
case leafReference, trunkReference, trunkReferences:
92
refNode = s2
93
if s3, ok := refNode.Value.(*textWrap); ok {
94
s2 = s3
95
}
96
}
97
98
// Already has delimiters that make parenthesis unnecessary.
99
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
100
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
101
if hasParens || hasBraces {
102
return s
103
}
104
}
105
if refNode != nil {
106
refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
107
return s
108
}
109
return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
110
}
111
112
// FormatValue prints the reflect.Value, taking extra care to avoid descending
113
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
114
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
115
if !v.IsValid() {
116
return nil
117
}
118
t := v.Type()
119
120
// Check slice element for cycles.
121
if parentKind == reflect.Slice {
122
ptrRef, visited := ptrs.Push(v.Addr())
123
if visited {
124
return makeLeafReference(ptrRef, false)
125
}
126
defer ptrs.Pop()
127
defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
128
}
129
130
// Check whether there is an Error or String method to call.
131
if !opts.AvoidStringer && v.CanInterface() {
132
// Avoid calling Error or String methods on nil receivers since many
133
// implementations crash when doing so.
134
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
135
var prefix, strVal string
136
func() {
137
// Swallow and ignore any panics from String or Error.
138
defer func() { recover() }()
139
switch v := v.Interface().(type) {
140
case error:
141
strVal = v.Error()
142
prefix = "e"
143
case fmt.Stringer:
144
strVal = v.String()
145
prefix = "s"
146
}
147
}()
148
if prefix != "" {
149
return opts.formatString(prefix, strVal)
150
}
151
}
152
}
153
154
// Check whether to explicitly wrap the result with the type.
155
var skipType bool
156
defer func() {
157
if !skipType {
158
out = opts.FormatType(t, out)
159
}
160
}()
161
162
switch t.Kind() {
163
case reflect.Bool:
164
return textLine(fmt.Sprint(v.Bool()))
165
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
166
return textLine(fmt.Sprint(v.Int()))
167
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
168
return textLine(fmt.Sprint(v.Uint()))
169
case reflect.Uint8:
170
if parentKind == reflect.Slice || parentKind == reflect.Array {
171
return textLine(formatHex(v.Uint()))
172
}
173
return textLine(fmt.Sprint(v.Uint()))
174
case reflect.Uintptr:
175
return textLine(formatHex(v.Uint()))
176
case reflect.Float32, reflect.Float64:
177
return textLine(fmt.Sprint(v.Float()))
178
case reflect.Complex64, reflect.Complex128:
179
return textLine(fmt.Sprint(v.Complex()))
180
case reflect.String:
181
return opts.formatString("", v.String())
182
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
183
return textLine(formatPointer(value.PointerOf(v), true))
184
case reflect.Struct:
185
var list textList
186
v := makeAddressable(v) // needed for retrieveUnexportedField
187
maxLen := v.NumField()
188
if opts.LimitVerbosity {
189
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
190
opts.VerbosityLevel--
191
}
192
for i := 0; i < v.NumField(); i++ {
193
vv := v.Field(i)
194
if vv.IsZero() {
195
continue // Elide fields with zero values
196
}
197
if len(list) == maxLen {
198
list.AppendEllipsis(diffStats{})
199
break
200
}
201
sf := t.Field(i)
202
if !isExported(sf.Name) {
203
vv = retrieveUnexportedField(v, sf, true)
204
}
205
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
206
list = append(list, textRecord{Key: sf.Name, Value: s})
207
}
208
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
209
case reflect.Slice:
210
if v.IsNil() {
211
return textNil
212
}
213
214
// Check whether this is a []byte of text data.
215
if t.Elem() == byteType {
216
b := v.Bytes()
217
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
218
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
219
out = opts.formatString("", string(b))
220
skipType = true
221
return opts.FormatType(t, out)
222
}
223
}
224
225
fallthrough
226
case reflect.Array:
227
maxLen := v.Len()
228
if opts.LimitVerbosity {
229
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
230
opts.VerbosityLevel--
231
}
232
var list textList
233
for i := 0; i < v.Len(); i++ {
234
if len(list) == maxLen {
235
list.AppendEllipsis(diffStats{})
236
break
237
}
238
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
239
list = append(list, textRecord{Value: s})
240
}
241
242
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
243
if t.Kind() == reflect.Slice && opts.PrintAddresses {
244
header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
245
out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
246
}
247
return out
248
case reflect.Map:
249
if v.IsNil() {
250
return textNil
251
}
252
253
// Check pointer for cycles.
254
ptrRef, visited := ptrs.Push(v)
255
if visited {
256
return makeLeafReference(ptrRef, opts.PrintAddresses)
257
}
258
defer ptrs.Pop()
259
260
maxLen := v.Len()
261
if opts.LimitVerbosity {
262
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
263
opts.VerbosityLevel--
264
}
265
var list textList
266
for _, k := range value.SortKeys(v.MapKeys()) {
267
if len(list) == maxLen {
268
list.AppendEllipsis(diffStats{})
269
break
270
}
271
sk := formatMapKey(k, false, ptrs)
272
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
273
list = append(list, textRecord{Key: sk, Value: sv})
274
}
275
276
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
277
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
278
return out
279
case reflect.Ptr:
280
if v.IsNil() {
281
return textNil
282
}
283
284
// Check pointer for cycles.
285
ptrRef, visited := ptrs.Push(v)
286
if visited {
287
out = makeLeafReference(ptrRef, opts.PrintAddresses)
288
return &textWrap{Prefix: "&", Value: out}
289
}
290
defer ptrs.Pop()
291
292
// Skip the name only if this is an unnamed pointer type.
293
// Otherwise taking the address of a value does not reproduce
294
// the named pointer type.
295
if v.Type().Name() == "" {
296
skipType = true // Let the underlying value print the type instead
297
}
298
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
299
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
300
out = &textWrap{Prefix: "&", Value: out}
301
return out
302
case reflect.Interface:
303
if v.IsNil() {
304
return textNil
305
}
306
// Interfaces accept different concrete types,
307
// so configure the underlying value to explicitly print the type.
308
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
309
default:
310
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
311
}
312
}
313
314
func (opts formatOptions) formatString(prefix, s string) textNode {
315
maxLen := len(s)
316
maxLines := strings.Count(s, "\n") + 1
317
if opts.LimitVerbosity {
318
maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
319
maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
320
}
321
322
// For multiline strings, use the triple-quote syntax,
323
// but only use it when printing removed or inserted nodes since
324
// we only want the extra verbosity for those cases.
325
lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
326
isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
327
for i := 0; i < len(lines) && isTripleQuoted; i++ {
328
lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
329
isPrintable := func(r rune) bool {
330
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
331
}
332
line := lines[i]
333
isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
334
}
335
if isTripleQuoted {
336
var list textList
337
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
338
for i, line := range lines {
339
if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
340
comment := commentString(fmt.Sprintf("%d elided lines", numElided))
341
list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
342
break
343
}
344
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
345
}
346
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
347
return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
348
}
349
350
// Format the string as a single-line quoted string.
351
if len(s) > maxLen+len(textEllipsis) {
352
return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
353
}
354
return textLine(prefix + formatString(s))
355
}
356
357
// formatMapKey formats v as if it were a map key.
358
// The result is guaranteed to be a single line.
359
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
360
var opts formatOptions
361
opts.DiffMode = diffIdentical
362
opts.TypeMode = elideType
363
opts.PrintAddresses = disambiguate
364
opts.AvoidStringer = disambiguate
365
opts.QualifiedNames = disambiguate
366
opts.VerbosityLevel = maxVerbosityPreset
367
opts.LimitVerbosity = true
368
s := opts.FormatValue(v, reflect.Map, ptrs).String()
369
return strings.TrimSpace(s)
370
}
371
372
// formatString prints s as a double-quoted or backtick-quoted string.
373
func formatString(s string) string {
374
// Use quoted string if it the same length as a raw string literal.
375
// Otherwise, attempt to use the raw string form.
376
qs := strconv.Quote(s)
377
if len(qs) == 1+len(s)+1 {
378
return qs
379
}
380
381
// Disallow newlines to ensure output is a single line.
382
// Only allow printable runes for readability purposes.
383
rawInvalid := func(r rune) bool {
384
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
385
}
386
if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
387
return "`" + s + "`"
388
}
389
return qs
390
}
391
392
// formatHex prints u as a hexadecimal integer in Go notation.
393
func formatHex(u uint64) string {
394
var f string
395
switch {
396
case u <= 0xff:
397
f = "0x%02x"
398
case u <= 0xffff:
399
f = "0x%04x"
400
case u <= 0xffffff:
401
f = "0x%06x"
402
case u <= 0xffffffff:
403
f = "0x%08x"
404
case u <= 0xffffffffff:
405
f = "0x%010x"
406
case u <= 0xffffffffffff:
407
f = "0x%012x"
408
case u <= 0xffffffffffffff:
409
f = "0x%014x"
410
case u <= 0xffffffffffffffff:
411
f = "0x%016x"
412
}
413
return fmt.Sprintf(f, u)
414
}
415
416