| // 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 |
| } |