mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	full diff: https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			415 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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 file.
 | 
						|
 | 
						|
package cmp
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"unicode"
 | 
						|
	"unicode/utf8"
 | 
						|
 | 
						|
	"github.com/google/go-cmp/cmp/internal/value"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	anyType    = reflect.TypeOf((*interface{})(nil)).Elem()
 | 
						|
	stringType = reflect.TypeOf((*string)(nil)).Elem()
 | 
						|
	bytesType  = reflect.TypeOf((*[]byte)(nil)).Elem()
 | 
						|
	byteType   = reflect.TypeOf((*byte)(nil)).Elem()
 | 
						|
)
 | 
						|
 | 
						|
type formatValueOptions struct {
 | 
						|
	// AvoidStringer controls whether to avoid calling custom stringer
 | 
						|
	// methods like error.Error or fmt.Stringer.String.
 | 
						|
	AvoidStringer bool
 | 
						|
 | 
						|
	// PrintAddresses controls whether to print the address of all pointers,
 | 
						|
	// slice elements, and maps.
 | 
						|
	PrintAddresses bool
 | 
						|
 | 
						|
	// QualifiedNames controls whether FormatType uses the fully qualified name
 | 
						|
	// (including the full package path as opposed to just the package name).
 | 
						|
	QualifiedNames bool
 | 
						|
 | 
						|
	// VerbosityLevel controls the amount of output to produce.
 | 
						|
	// A higher value produces more output. A value of zero or lower produces
 | 
						|
	// no output (represented using an ellipsis).
 | 
						|
	// If LimitVerbosity is false, then the level is treated as infinite.
 | 
						|
	VerbosityLevel int
 | 
						|
 | 
						|
	// LimitVerbosity specifies that formatting should respect VerbosityLevel.
 | 
						|
	LimitVerbosity 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
 | 
						|
		}
 | 
						|
		if opts.DiffMode == diffIdentical {
 | 
						|
			return s // elide type for identical nodes
 | 
						|
		}
 | 
						|
	case elideType:
 | 
						|
		return s
 | 
						|
	}
 | 
						|
 | 
						|
	// Determine the type label, applying special handling for unnamed types.
 | 
						|
	typeName := value.TypeString(t, opts.QualifiedNames)
 | 
						|
	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 + ")"
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return &textWrap{Prefix: typeName, Value: wrapParens(s)}
 | 
						|
}
 | 
						|
 | 
						|
// wrapParens wraps s with a set of parenthesis, but avoids it if the
 | 
						|
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
 | 
						|
// It handles unwrapping one level of pointer-reference nodes.
 | 
						|
func wrapParens(s textNode) textNode {
 | 
						|
	var refNode *textWrap
 | 
						|
	if s2, ok := s.(*textWrap); ok {
 | 
						|
		// Unwrap a single pointer reference node.
 | 
						|
		switch s2.Metadata.(type) {
 | 
						|
		case leafReference, trunkReference, trunkReferences:
 | 
						|
			refNode = s2
 | 
						|
			if s3, ok := refNode.Value.(*textWrap); ok {
 | 
						|
				s2 = s3
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Already has delimiters that make parenthesis unnecessary.
 | 
						|
		hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
 | 
						|
		hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
 | 
						|
		if hasParens || hasBraces {
 | 
						|
			return s
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if refNode != nil {
 | 
						|
		refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
 | 
						|
		return s
 | 
						|
	}
 | 
						|
	return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
 | 
						|
}
 | 
						|
 | 
						|
// FormatValue prints the reflect.Value, taking extra care to avoid descending
 | 
						|
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
 | 
						|
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
 | 
						|
	if !v.IsValid() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	t := v.Type()
 | 
						|
 | 
						|
	// Check slice element for cycles.
 | 
						|
	if parentKind == reflect.Slice {
 | 
						|
		ptrRef, visited := ptrs.Push(v.Addr())
 | 
						|
		if visited {
 | 
						|
			return makeLeafReference(ptrRef, false)
 | 
						|
		}
 | 
						|
		defer ptrs.Pop()
 | 
						|
		defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
 | 
						|
	}
 | 
						|
 | 
						|
	// 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() {
 | 
						|
			var prefix, strVal string
 | 
						|
			func() {
 | 
						|
				// Swallow and ignore any panics from String or Error.
 | 
						|
				defer func() { recover() }()
 | 
						|
				switch v := v.Interface().(type) {
 | 
						|
				case error:
 | 
						|
					strVal = v.Error()
 | 
						|
					prefix = "e"
 | 
						|
				case fmt.Stringer:
 | 
						|
					strVal = v.String()
 | 
						|
					prefix = "s"
 | 
						|
				}
 | 
						|
			}()
 | 
						|
			if prefix != "" {
 | 
						|
				return opts.formatString(prefix, strVal)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Check whether to explicitly wrap the result with the type.
 | 
						|
	var skipType bool
 | 
						|
	defer func() {
 | 
						|
		if !skipType {
 | 
						|
			out = opts.FormatType(t, out)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	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.Uint16, reflect.Uint32, reflect.Uint64:
 | 
						|
		return textLine(fmt.Sprint(v.Uint()))
 | 
						|
	case reflect.Uint8:
 | 
						|
		if parentKind == reflect.Slice || parentKind == reflect.Array {
 | 
						|
			return textLine(formatHex(v.Uint()))
 | 
						|
		}
 | 
						|
		return textLine(fmt.Sprint(v.Uint()))
 | 
						|
	case reflect.Uintptr:
 | 
						|
		return textLine(formatHex(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 opts.formatString("", v.String())
 | 
						|
	case reflect.UnsafePointer, reflect.Chan, reflect.Func:
 | 
						|
		return textLine(formatPointer(value.PointerOf(v), true))
 | 
						|
	case reflect.Struct:
 | 
						|
		var list textList
 | 
						|
		v := makeAddressable(v) // needed for retrieveUnexportedField
 | 
						|
		maxLen := v.NumField()
 | 
						|
		if opts.LimitVerbosity {
 | 
						|
			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
 | 
						|
			opts.VerbosityLevel--
 | 
						|
		}
 | 
						|
		for i := 0; i < v.NumField(); i++ {
 | 
						|
			vv := v.Field(i)
 | 
						|
			if vv.IsZero() {
 | 
						|
				continue // Elide fields with zero values
 | 
						|
			}
 | 
						|
			if len(list) == maxLen {
 | 
						|
				list.AppendEllipsis(diffStats{})
 | 
						|
				break
 | 
						|
			}
 | 
						|
			sf := t.Field(i)
 | 
						|
			if !isExported(sf.Name) {
 | 
						|
				vv = retrieveUnexportedField(v, sf, true)
 | 
						|
			}
 | 
						|
			s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
 | 
						|
			list = append(list, textRecord{Key: sf.Name, Value: s})
 | 
						|
		}
 | 
						|
		return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
 | 
						|
	case reflect.Slice:
 | 
						|
		if v.IsNil() {
 | 
						|
			return textNil
 | 
						|
		}
 | 
						|
 | 
						|
		// Check whether this is a []byte of text data.
 | 
						|
		if t.Elem() == byteType {
 | 
						|
			b := v.Bytes()
 | 
						|
			isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
 | 
						|
			if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
 | 
						|
				out = opts.formatString("", string(b))
 | 
						|
				skipType = true
 | 
						|
				return opts.FormatType(t, out)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		fallthrough
 | 
						|
	case reflect.Array:
 | 
						|
		maxLen := v.Len()
 | 
						|
		if opts.LimitVerbosity {
 | 
						|
			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
 | 
						|
			opts.VerbosityLevel--
 | 
						|
		}
 | 
						|
		var list textList
 | 
						|
		for i := 0; i < v.Len(); i++ {
 | 
						|
			if len(list) == maxLen {
 | 
						|
				list.AppendEllipsis(diffStats{})
 | 
						|
				break
 | 
						|
			}
 | 
						|
			s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
 | 
						|
			list = append(list, textRecord{Value: s})
 | 
						|
		}
 | 
						|
 | 
						|
		out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
 | 
						|
		if t.Kind() == reflect.Slice && opts.PrintAddresses {
 | 
						|
			header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
 | 
						|
			out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
 | 
						|
		}
 | 
						|
		return out
 | 
						|
	case reflect.Map:
 | 
						|
		if v.IsNil() {
 | 
						|
			return textNil
 | 
						|
		}
 | 
						|
 | 
						|
		// Check pointer for cycles.
 | 
						|
		ptrRef, visited := ptrs.Push(v)
 | 
						|
		if visited {
 | 
						|
			return makeLeafReference(ptrRef, opts.PrintAddresses)
 | 
						|
		}
 | 
						|
		defer ptrs.Pop()
 | 
						|
 | 
						|
		maxLen := v.Len()
 | 
						|
		if opts.LimitVerbosity {
 | 
						|
			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
 | 
						|
			opts.VerbosityLevel--
 | 
						|
		}
 | 
						|
		var list textList
 | 
						|
		for _, k := range value.SortKeys(v.MapKeys()) {
 | 
						|
			if len(list) == maxLen {
 | 
						|
				list.AppendEllipsis(diffStats{})
 | 
						|
				break
 | 
						|
			}
 | 
						|
			sk := formatMapKey(k, false, ptrs)
 | 
						|
			sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
 | 
						|
			list = append(list, textRecord{Key: sk, Value: sv})
 | 
						|
		}
 | 
						|
 | 
						|
		out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
 | 
						|
		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
 | 
						|
		return out
 | 
						|
	case reflect.Ptr:
 | 
						|
		if v.IsNil() {
 | 
						|
			return textNil
 | 
						|
		}
 | 
						|
 | 
						|
		// Check pointer for cycles.
 | 
						|
		ptrRef, visited := ptrs.Push(v)
 | 
						|
		if visited {
 | 
						|
			out = makeLeafReference(ptrRef, opts.PrintAddresses)
 | 
						|
			return &textWrap{Prefix: "&", Value: out}
 | 
						|
		}
 | 
						|
		defer ptrs.Pop()
 | 
						|
 | 
						|
		// Skip the name only if this is an unnamed pointer type.
 | 
						|
		// Otherwise taking the address of a value does not reproduce
 | 
						|
		// the named pointer type.
 | 
						|
		if v.Type().Name() == "" {
 | 
						|
			skipType = true // Let the underlying value print the type instead
 | 
						|
		}
 | 
						|
		out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
 | 
						|
		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
 | 
						|
		out = &textWrap{Prefix: "&", Value: out}
 | 
						|
		return out
 | 
						|
	case reflect.Interface:
 | 
						|
		if v.IsNil() {
 | 
						|
			return textNil
 | 
						|
		}
 | 
						|
		// Interfaces accept different concrete types,
 | 
						|
		// so configure the underlying value to explicitly print the type.
 | 
						|
		return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
 | 
						|
	default:
 | 
						|
		panic(fmt.Sprintf("%v kind not handled", v.Kind()))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (opts formatOptions) formatString(prefix, s string) textNode {
 | 
						|
	maxLen := len(s)
 | 
						|
	maxLines := strings.Count(s, "\n") + 1
 | 
						|
	if opts.LimitVerbosity {
 | 
						|
		maxLen = (1 << opts.verbosity()) << 5   // 32, 64, 128, 256, etc...
 | 
						|
		maxLines = (1 << opts.verbosity()) << 2 //  4, 8, 16, 32, 64, etc...
 | 
						|
	}
 | 
						|
 | 
						|
	// For multiline strings, use the triple-quote syntax,
 | 
						|
	// but only use it when printing removed or inserted nodes since
 | 
						|
	// we only want the extra verbosity for those cases.
 | 
						|
	lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
 | 
						|
	isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
 | 
						|
	for i := 0; i < len(lines) && isTripleQuoted; i++ {
 | 
						|
		lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
 | 
						|
		isPrintable := func(r rune) bool {
 | 
						|
			return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
 | 
						|
		}
 | 
						|
		line := lines[i]
 | 
						|
		isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
 | 
						|
	}
 | 
						|
	if isTripleQuoted {
 | 
						|
		var list textList
 | 
						|
		list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
 | 
						|
		for i, line := range lines {
 | 
						|
			if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
 | 
						|
				comment := commentString(fmt.Sprintf("%d elided lines", numElided))
 | 
						|
				list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
 | 
						|
				break
 | 
						|
			}
 | 
						|
			list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
 | 
						|
		}
 | 
						|
		list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
 | 
						|
		return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
 | 
						|
	}
 | 
						|
 | 
						|
	// Format the string as a single-line quoted string.
 | 
						|
	if len(s) > maxLen+len(textEllipsis) {
 | 
						|
		return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
 | 
						|
	}
 | 
						|
	return textLine(prefix + formatString(s))
 | 
						|
}
 | 
						|
 | 
						|
// formatMapKey formats v as if it were a map key.
 | 
						|
// The result is guaranteed to be a single line.
 | 
						|
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
 | 
						|
	var opts formatOptions
 | 
						|
	opts.DiffMode = diffIdentical
 | 
						|
	opts.TypeMode = elideType
 | 
						|
	opts.PrintAddresses = disambiguate
 | 
						|
	opts.AvoidStringer = disambiguate
 | 
						|
	opts.QualifiedNames = disambiguate
 | 
						|
	opts.VerbosityLevel = maxVerbosityPreset
 | 
						|
	opts.LimitVerbosity = true
 | 
						|
	s := opts.FormatValue(v, reflect.Map, ptrs).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 utf8.ValidString(s) && 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)
 | 
						|
}
 |