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