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