Path: blob/main/vendor/github.com/google/go-cmp/cmp/report_reflect.go
2880 views
// Copyright 2019, The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34package cmp56import (7"bytes"8"fmt"9"reflect"10"strconv"11"strings"12"unicode"13"unicode/utf8"1415"github.com/google/go-cmp/cmp/internal/value"16)1718var (19anyType = reflect.TypeOf((*interface{})(nil)).Elem()20stringType = reflect.TypeOf((*string)(nil)).Elem()21bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()22byteType = reflect.TypeOf((*byte)(nil)).Elem()23)2425type formatValueOptions struct {26// AvoidStringer controls whether to avoid calling custom stringer27// methods like error.Error or fmt.Stringer.String.28AvoidStringer bool2930// PrintAddresses controls whether to print the address of all pointers,31// slice elements, and maps.32PrintAddresses bool3334// QualifiedNames controls whether FormatType uses the fully qualified name35// (including the full package path as opposed to just the package name).36QualifiedNames bool3738// VerbosityLevel controls the amount of output to produce.39// A higher value produces more output. A value of zero or lower produces40// no output (represented using an ellipsis).41// If LimitVerbosity is false, then the level is treated as infinite.42VerbosityLevel int4344// LimitVerbosity specifies that formatting should respect VerbosityLevel.45LimitVerbosity bool46}4748// FormatType prints the type as if it were wrapping s.49// This may return s as-is depending on the current type and TypeMode mode.50func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {51// Check whether to emit the type or not.52switch opts.TypeMode {53case autoType:54switch t.Kind() {55case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:56if s.Equal(textNil) {57return s58}59default:60return s61}62if opts.DiffMode == diffIdentical {63return s // elide type for identical nodes64}65case elideType:66return s67}6869// Determine the type label, applying special handling for unnamed types.70typeName := value.TypeString(t, opts.QualifiedNames)71if t.Name() == "" {72// According to Go grammar, certain type literals contain symbols that73// do not strongly bind to the next lexicographical token (e.g., *T).74switch t.Kind() {75case reflect.Chan, reflect.Func, reflect.Ptr:76typeName = "(" + typeName + ")"77}78}79return &textWrap{Prefix: typeName, Value: wrapParens(s)}80}8182// wrapParens wraps s with a set of parenthesis, but avoids it if the83// wrapped node itself is already surrounded by a pair of parenthesis or braces.84// It handles unwrapping one level of pointer-reference nodes.85func wrapParens(s textNode) textNode {86var refNode *textWrap87if s2, ok := s.(*textWrap); ok {88// Unwrap a single pointer reference node.89switch s2.Metadata.(type) {90case leafReference, trunkReference, trunkReferences:91refNode = s292if s3, ok := refNode.Value.(*textWrap); ok {93s2 = s394}95}9697// Already has delimiters that make parenthesis unnecessary.98hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")99hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")100if hasParens || hasBraces {101return s102}103}104if refNode != nil {105refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}106return s107}108return &textWrap{Prefix: "(", Value: s, Suffix: ")"}109}110111// FormatValue prints the reflect.Value, taking extra care to avoid descending112// into pointers already in ptrs. As pointers are visited, ptrs is also updated.113func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {114if !v.IsValid() {115return nil116}117t := v.Type()118119// Check slice element for cycles.120if parentKind == reflect.Slice {121ptrRef, visited := ptrs.Push(v.Addr())122if visited {123return makeLeafReference(ptrRef, false)124}125defer ptrs.Pop()126defer func() { out = wrapTrunkReference(ptrRef, false, out) }()127}128129// Check whether there is an Error or String method to call.130if !opts.AvoidStringer && v.CanInterface() {131// Avoid calling Error or String methods on nil receivers since many132// implementations crash when doing so.133if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {134var prefix, strVal string135func() {136// Swallow and ignore any panics from String or Error.137defer func() { recover() }()138switch v := v.Interface().(type) {139case error:140strVal = v.Error()141prefix = "e"142case fmt.Stringer:143strVal = v.String()144prefix = "s"145}146}()147if prefix != "" {148return opts.formatString(prefix, strVal)149}150}151}152153// Check whether to explicitly wrap the result with the type.154var skipType bool155defer func() {156if !skipType {157out = opts.FormatType(t, out)158}159}()160161switch t.Kind() {162case reflect.Bool:163return textLine(fmt.Sprint(v.Bool()))164case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:165return textLine(fmt.Sprint(v.Int()))166case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:167return textLine(fmt.Sprint(v.Uint()))168case reflect.Uint8:169if parentKind == reflect.Slice || parentKind == reflect.Array {170return textLine(formatHex(v.Uint()))171}172return textLine(fmt.Sprint(v.Uint()))173case reflect.Uintptr:174return textLine(formatHex(v.Uint()))175case reflect.Float32, reflect.Float64:176return textLine(fmt.Sprint(v.Float()))177case reflect.Complex64, reflect.Complex128:178return textLine(fmt.Sprint(v.Complex()))179case reflect.String:180return opts.formatString("", v.String())181case reflect.UnsafePointer, reflect.Chan, reflect.Func:182return textLine(formatPointer(value.PointerOf(v), true))183case reflect.Struct:184var list textList185v := makeAddressable(v) // needed for retrieveUnexportedField186maxLen := v.NumField()187if opts.LimitVerbosity {188maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...189opts.VerbosityLevel--190}191for i := 0; i < v.NumField(); i++ {192vv := v.Field(i)193if vv.IsZero() {194continue // Elide fields with zero values195}196if len(list) == maxLen {197list.AppendEllipsis(diffStats{})198break199}200sf := t.Field(i)201if !isExported(sf.Name) {202vv = retrieveUnexportedField(v, sf, true)203}204s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)205list = append(list, textRecord{Key: sf.Name, Value: s})206}207return &textWrap{Prefix: "{", Value: list, Suffix: "}"}208case reflect.Slice:209if v.IsNil() {210return textNil211}212213// Check whether this is a []byte of text data.214if t.Elem() == byteType {215b := v.Bytes()216isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }217if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {218out = opts.formatString("", string(b))219skipType = true220return opts.FormatType(t, out)221}222}223224fallthrough225case reflect.Array:226maxLen := v.Len()227if opts.LimitVerbosity {228maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...229opts.VerbosityLevel--230}231var list textList232for i := 0; i < v.Len(); i++ {233if len(list) == maxLen {234list.AppendEllipsis(diffStats{})235break236}237s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)238list = append(list, textRecord{Value: s})239}240241out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}242if t.Kind() == reflect.Slice && opts.PrintAddresses {243header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())244out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}245}246return out247case reflect.Map:248if v.IsNil() {249return textNil250}251252// Check pointer for cycles.253ptrRef, visited := ptrs.Push(v)254if visited {255return makeLeafReference(ptrRef, opts.PrintAddresses)256}257defer ptrs.Pop()258259maxLen := v.Len()260if opts.LimitVerbosity {261maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...262opts.VerbosityLevel--263}264var list textList265for _, k := range value.SortKeys(v.MapKeys()) {266if len(list) == maxLen {267list.AppendEllipsis(diffStats{})268break269}270sk := formatMapKey(k, false, ptrs)271sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)272list = append(list, textRecord{Key: sk, Value: sv})273}274275out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}276out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)277return out278case reflect.Ptr:279if v.IsNil() {280return textNil281}282283// Check pointer for cycles.284ptrRef, visited := ptrs.Push(v)285if visited {286out = makeLeafReference(ptrRef, opts.PrintAddresses)287return &textWrap{Prefix: "&", Value: out}288}289defer ptrs.Pop()290291// Skip the name only if this is an unnamed pointer type.292// Otherwise taking the address of a value does not reproduce293// the named pointer type.294if v.Type().Name() == "" {295skipType = true // Let the underlying value print the type instead296}297out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)298out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)299out = &textWrap{Prefix: "&", Value: out}300return out301case reflect.Interface:302if v.IsNil() {303return textNil304}305// Interfaces accept different concrete types,306// so configure the underlying value to explicitly print the type.307return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)308default:309panic(fmt.Sprintf("%v kind not handled", v.Kind()))310}311}312313func (opts formatOptions) formatString(prefix, s string) textNode {314maxLen := len(s)315maxLines := strings.Count(s, "\n") + 1316if opts.LimitVerbosity {317maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...318maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...319}320321// For multiline strings, use the triple-quote syntax,322// but only use it when printing removed or inserted nodes since323// we only want the extra verbosity for those cases.324lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")325isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')326for i := 0; i < len(lines) && isTripleQuoted; i++ {327lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support328isPrintable := func(r rune) bool {329return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable330}331line := lines[i]332isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen333}334if isTripleQuoted {335var list textList336list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})337for i, line := range lines {338if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {339comment := commentString(fmt.Sprintf("%d elided lines", numElided))340list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})341break342}343list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})344}345list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})346return &textWrap{Prefix: "(", Value: list, Suffix: ")"}347}348349// Format the string as a single-line quoted string.350if len(s) > maxLen+len(textEllipsis) {351return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))352}353return textLine(prefix + formatString(s))354}355356// formatMapKey formats v as if it were a map key.357// The result is guaranteed to be a single line.358func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {359var opts formatOptions360opts.DiffMode = diffIdentical361opts.TypeMode = elideType362opts.PrintAddresses = disambiguate363opts.AvoidStringer = disambiguate364opts.QualifiedNames = disambiguate365opts.VerbosityLevel = maxVerbosityPreset366opts.LimitVerbosity = true367s := opts.FormatValue(v, reflect.Map, ptrs).String()368return strings.TrimSpace(s)369}370371// formatString prints s as a double-quoted or backtick-quoted string.372func formatString(s string) string {373// Use quoted string if it the same length as a raw string literal.374// Otherwise, attempt to use the raw string form.375qs := strconv.Quote(s)376if len(qs) == 1+len(s)+1 {377return qs378}379380// Disallow newlines to ensure output is a single line.381// Only allow printable runes for readability purposes.382rawInvalid := func(r rune) bool {383return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')384}385if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {386return "`" + s + "`"387}388return qs389}390391// formatHex prints u as a hexadecimal integer in Go notation.392func formatHex(u uint64) string {393var f string394switch {395case u <= 0xff:396f = "0x%02x"397case u <= 0xffff:398f = "0x%04x"399case u <= 0xffffff:400f = "0x%06x"401case u <= 0xffffffff:402f = "0x%08x"403case u <= 0xffffffffff:404f = "0x%010x"405case u <= 0xffffffffffff:406f = "0x%012x"407case u <= 0xffffffffffffff:408f = "0x%014x"409case u <= 0xffffffffffffffff:410f = "0x%016x"411}412return fmt.Sprintf(f, u)413}414415416