123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- // Copyright 2019, The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE.md file.
- package cmp
- import (
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "unicode"
- "github.com/google/go-cmp/cmp/internal/flags"
- "github.com/google/go-cmp/cmp/internal/value"
- )
- type formatValueOptions struct {
- // AvoidStringer controls whether to avoid calling custom stringer
- // methods like error.Error or fmt.Stringer.String.
- AvoidStringer bool
- // ShallowPointers controls whether to avoid descending into pointers.
- // Useful when printing map keys, where pointer comparison is performed
- // on the pointer address rather than the pointed-at value.
- ShallowPointers bool
- // PrintAddresses controls whether to print the address of all pointers,
- // slice elements, and maps.
- PrintAddresses bool
- }
- // FormatType prints the type as if it were wrapping s.
- // This may return s as-is depending on the current type and TypeMode mode.
- func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
- // Check whether to emit the type or not.
- switch opts.TypeMode {
- case autoType:
- switch t.Kind() {
- case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
- if s.Equal(textNil) {
- return s
- }
- default:
- return s
- }
- case elideType:
- return s
- }
- // Determine the type label, applying special handling for unnamed types.
- typeName := t.String()
- if t.Name() == "" {
- // According to Go grammar, certain type literals contain symbols that
- // do not strongly bind to the next lexicographical token (e.g., *T).
- switch t.Kind() {
- case reflect.Chan, reflect.Func, reflect.Ptr:
- typeName = "(" + typeName + ")"
- }
- typeName = strings.Replace(typeName, "struct {", "struct{", -1)
- typeName = strings.Replace(typeName, "interface {", "interface{", -1)
- }
- // Avoid wrap the value in parenthesis if unnecessary.
- if s, ok := s.(textWrap); ok {
- hasParens := strings.HasPrefix(s.Prefix, "(") && strings.HasSuffix(s.Suffix, ")")
- hasBraces := strings.HasPrefix(s.Prefix, "{") && strings.HasSuffix(s.Suffix, "}")
- if hasParens || hasBraces {
- return textWrap{typeName, s, ""}
- }
- }
- return textWrap{typeName + "(", s, ")"}
- }
- // FormatValue prints the reflect.Value, taking extra care to avoid descending
- // into pointers already in m. As pointers are visited, m is also updated.
- func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) {
- if !v.IsValid() {
- return nil
- }
- t := v.Type()
- // Check whether there is an Error or String method to call.
- if !opts.AvoidStringer && v.CanInterface() {
- // Avoid calling Error or String methods on nil receivers since many
- // implementations crash when doing so.
- if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
- switch v := v.Interface().(type) {
- case error:
- return textLine("e" + formatString(v.Error()))
- case fmt.Stringer:
- return textLine("s" + formatString(v.String()))
- }
- }
- }
- // Check whether to explicitly wrap the result with the type.
- var skipType bool
- defer func() {
- if !skipType {
- out = opts.FormatType(t, out)
- }
- }()
- var ptr string
- switch t.Kind() {
- case reflect.Bool:
- return textLine(fmt.Sprint(v.Bool()))
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return textLine(fmt.Sprint(v.Int()))
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- // Unnamed uints are usually bytes or words, so use hexadecimal.
- if t.PkgPath() == "" || t.Kind() == reflect.Uintptr {
- return textLine(formatHex(v.Uint()))
- }
- return textLine(fmt.Sprint(v.Uint()))
- case reflect.Float32, reflect.Float64:
- return textLine(fmt.Sprint(v.Float()))
- case reflect.Complex64, reflect.Complex128:
- return textLine(fmt.Sprint(v.Complex()))
- case reflect.String:
- return textLine(formatString(v.String()))
- case reflect.UnsafePointer, reflect.Chan, reflect.Func:
- return textLine(formatPointer(v))
- case reflect.Struct:
- var list textList
- for i := 0; i < v.NumField(); i++ {
- vv := v.Field(i)
- if value.IsZero(vv) {
- continue // Elide fields with zero values
- }
- s := opts.WithTypeMode(autoType).FormatValue(vv, m)
- list = append(list, textRecord{Key: t.Field(i).Name, Value: s})
- }
- return textWrap{"{", list, "}"}
- case reflect.Slice:
- if v.IsNil() {
- return textNil
- }
- if opts.PrintAddresses {
- ptr = formatPointer(v)
- }
- fallthrough
- case reflect.Array:
- var list textList
- for i := 0; i < v.Len(); i++ {
- vi := v.Index(i)
- if vi.CanAddr() { // Check for cyclic elements
- p := vi.Addr()
- if m.Visit(p) {
- var out textNode
- out = textLine(formatPointer(p))
- out = opts.WithTypeMode(emitType).FormatType(p.Type(), out)
- out = textWrap{"*", out, ""}
- list = append(list, textRecord{Value: out})
- continue
- }
- }
- s := opts.WithTypeMode(elideType).FormatValue(vi, m)
- list = append(list, textRecord{Value: s})
- }
- return textWrap{ptr + "{", list, "}"}
- case reflect.Map:
- if v.IsNil() {
- return textNil
- }
- if m.Visit(v) {
- return textLine(formatPointer(v))
- }
- var list textList
- for _, k := range value.SortKeys(v.MapKeys()) {
- sk := formatMapKey(k)
- sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m)
- list = append(list, textRecord{Key: sk, Value: sv})
- }
- if opts.PrintAddresses {
- ptr = formatPointer(v)
- }
- return textWrap{ptr + "{", list, "}"}
- case reflect.Ptr:
- if v.IsNil() {
- return textNil
- }
- if m.Visit(v) || opts.ShallowPointers {
- return textLine(formatPointer(v))
- }
- if opts.PrintAddresses {
- ptr = formatPointer(v)
- }
- skipType = true // Let the underlying value print the type instead
- return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""}
- case reflect.Interface:
- if v.IsNil() {
- return textNil
- }
- // Interfaces accept different concrete types,
- // so configure the underlying value to explicitly print the type.
- skipType = true // Print the concrete type instead
- return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m)
- default:
- panic(fmt.Sprintf("%v kind not handled", v.Kind()))
- }
- }
- // formatMapKey formats v as if it were a map key.
- // The result is guaranteed to be a single line.
- func formatMapKey(v reflect.Value) string {
- var opts formatOptions
- opts.TypeMode = elideType
- opts.ShallowPointers = true
- s := opts.FormatValue(v, visitedPointers{}).String()
- return strings.TrimSpace(s)
- }
- // formatString prints s as a double-quoted or backtick-quoted string.
- func formatString(s string) string {
- // Use quoted string if it the same length as a raw string literal.
- // Otherwise, attempt to use the raw string form.
- qs := strconv.Quote(s)
- if len(qs) == 1+len(s)+1 {
- return qs
- }
- // Disallow newlines to ensure output is a single line.
- // Only allow printable runes for readability purposes.
- rawInvalid := func(r rune) bool {
- return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
- }
- if strings.IndexFunc(s, rawInvalid) < 0 {
- return "`" + s + "`"
- }
- return qs
- }
- // formatHex prints u as a hexadecimal integer in Go notation.
- func formatHex(u uint64) string {
- var f string
- switch {
- case u <= 0xff:
- f = "0x%02x"
- case u <= 0xffff:
- f = "0x%04x"
- case u <= 0xffffff:
- f = "0x%06x"
- case u <= 0xffffffff:
- f = "0x%08x"
- case u <= 0xffffffffff:
- f = "0x%010x"
- case u <= 0xffffffffffff:
- f = "0x%012x"
- case u <= 0xffffffffffffff:
- f = "0x%014x"
- case u <= 0xffffffffffffffff:
- f = "0x%016x"
- }
- return fmt.Sprintf(f, u)
- }
- // formatPointer prints the address of the pointer.
- func formatPointer(v reflect.Value) string {
- p := v.Pointer()
- if flags.Deterministic {
- p = 0xdeadf00f // Only used for stable testing purposes
- }
- return fmt.Sprintf("⟪0x%x⟫", p)
- }
- type visitedPointers map[value.Pointer]struct{}
- // Visit inserts pointer v into the visited map and reports whether it had
- // already been visited before.
- func (m visitedPointers) Visit(v reflect.Value) bool {
- p := value.PointerOf(v)
- _, visited := m[p]
- m[p] = struct{}{}
- return visited
- }
|