Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / github.com / google / go-cmp@v0.5.1 / cmp / report_compare.go
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.md file.
4
5 package cmp
6
7 import (
8         "fmt"
9         "reflect"
10
11         "github.com/google/go-cmp/cmp/internal/value"
12 )
13
14 // numContextRecords is the number of surrounding equal records to print.
15 const numContextRecords = 2
16
17 type diffMode byte
18
19 const (
20         diffUnknown   diffMode = 0
21         diffIdentical diffMode = ' '
22         diffRemoved   diffMode = '-'
23         diffInserted  diffMode = '+'
24 )
25
26 type typeMode int
27
28 const (
29         // emitType always prints the type.
30         emitType typeMode = iota
31         // elideType never prints the type.
32         elideType
33         // autoType prints the type only for composite kinds
34         // (i.e., structs, slices, arrays, and maps).
35         autoType
36 )
37
38 type formatOptions struct {
39         // DiffMode controls the output mode of FormatDiff.
40         //
41         // If diffUnknown,   then produce a diff of the x and y values.
42         // If diffIdentical, then emit values as if they were equal.
43         // If diffRemoved,   then only emit x values (ignoring y values).
44         // If diffInserted,  then only emit y values (ignoring x values).
45         DiffMode diffMode
46
47         // TypeMode controls whether to print the type for the current node.
48         //
49         // As a general rule of thumb, we always print the type of the next node
50         // after an interface, and always elide the type of the next node after
51         // a slice or map node.
52         TypeMode typeMode
53
54         // formatValueOptions are options specific to printing reflect.Values.
55         formatValueOptions
56 }
57
58 func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
59         opts.DiffMode = d
60         return opts
61 }
62 func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
63         opts.TypeMode = t
64         return opts
65 }
66 func (opts formatOptions) WithVerbosity(level int) formatOptions {
67         opts.VerbosityLevel = level
68         opts.LimitVerbosity = true
69         return opts
70 }
71 func (opts formatOptions) verbosity() uint {
72         switch {
73         case opts.VerbosityLevel < 0:
74                 return 0
75         case opts.VerbosityLevel > 16:
76                 return 16 // some reasonable maximum to avoid shift overflow
77         default:
78                 return uint(opts.VerbosityLevel)
79         }
80 }
81
82 const maxVerbosityPreset = 3
83
84 // verbosityPreset modifies the verbosity settings given an index
85 // between 0 and maxVerbosityPreset, inclusive.
86 func verbosityPreset(opts formatOptions, i int) formatOptions {
87         opts.VerbosityLevel = int(opts.verbosity()) + 2*i
88         if i > 0 {
89                 opts.AvoidStringer = true
90         }
91         if i >= maxVerbosityPreset {
92                 opts.PrintAddresses = true
93                 opts.QualifiedNames = true
94         }
95         return opts
96 }
97
98 // FormatDiff converts a valueNode tree into a textNode tree, where the later
99 // is a textual representation of the differences detected in the former.
100 func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
101         if opts.DiffMode == diffIdentical {
102                 opts = opts.WithVerbosity(1)
103         } else {
104                 opts = opts.WithVerbosity(3)
105         }
106
107         // Check whether we have specialized formatting for this node.
108         // This is not necessary, but helpful for producing more readable outputs.
109         if opts.CanFormatDiffSlice(v) {
110                 return opts.FormatDiffSlice(v)
111         }
112
113         var parentKind reflect.Kind
114         if v.parent != nil && v.parent.TransformerName == "" {
115                 parentKind = v.parent.Type.Kind()
116         }
117
118         // For leaf nodes, format the value based on the reflect.Values alone.
119         if v.MaxDepth == 0 {
120                 switch opts.DiffMode {
121                 case diffUnknown, diffIdentical:
122                         // Format Equal.
123                         if v.NumDiff == 0 {
124                                 outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
125                                 outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
126                                 if v.NumIgnored > 0 && v.NumSame == 0 {
127                                         return textEllipsis
128                                 } else if outx.Len() < outy.Len() {
129                                         return outx
130                                 } else {
131                                         return outy
132                                 }
133                         }
134
135                         // Format unequal.
136                         assert(opts.DiffMode == diffUnknown)
137                         var list textList
138                         outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
139                         outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
140                         for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
141                                 opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
142                                 outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
143                                 outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
144                         }
145                         if outx != nil {
146                                 list = append(list, textRecord{Diff: '-', Value: outx})
147                         }
148                         if outy != nil {
149                                 list = append(list, textRecord{Diff: '+', Value: outy})
150                         }
151                         return opts.WithTypeMode(emitType).FormatType(v.Type, list)
152                 case diffRemoved:
153                         return opts.FormatValue(v.ValueX, parentKind, ptrs)
154                 case diffInserted:
155                         return opts.FormatValue(v.ValueY, parentKind, ptrs)
156                 default:
157                         panic("invalid diff mode")
158                 }
159         }
160
161         // Register slice element to support cycle detection.
162         if parentKind == reflect.Slice {
163                 ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
164                 defer ptrs.Pop()
165                 defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
166         }
167
168         // Descend into the child value node.
169         if v.TransformerName != "" {
170                 out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
171                 out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
172                 return opts.FormatType(v.Type, out)
173         } else {
174                 switch k := v.Type.Kind(); k {
175                 case reflect.Struct, reflect.Array, reflect.Slice:
176                         out = opts.formatDiffList(v.Records, k, ptrs)
177                         out = opts.FormatType(v.Type, out)
178                 case reflect.Map:
179                         // Register map to support cycle detection.
180                         ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
181                         defer ptrs.Pop()
182
183                         out = opts.formatDiffList(v.Records, k, ptrs)
184                         out = wrapTrunkReferences(ptrRefs, out)
185                         out = opts.FormatType(v.Type, out)
186                 case reflect.Ptr:
187                         // Register pointer to support cycle detection.
188                         ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
189                         defer ptrs.Pop()
190
191                         out = opts.FormatDiff(v.Value, ptrs)
192                         out = wrapTrunkReferences(ptrRefs, out)
193                         out = &textWrap{Prefix: "&", Value: out}
194                 case reflect.Interface:
195                         out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
196                 default:
197                         panic(fmt.Sprintf("%v cannot have children", k))
198                 }
199                 return out
200         }
201 }
202
203 func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
204         // Derive record name based on the data structure kind.
205         var name string
206         var formatKey func(reflect.Value) string
207         switch k {
208         case reflect.Struct:
209                 name = "field"
210                 opts = opts.WithTypeMode(autoType)
211                 formatKey = func(v reflect.Value) string { return v.String() }
212         case reflect.Slice, reflect.Array:
213                 name = "element"
214                 opts = opts.WithTypeMode(elideType)
215                 formatKey = func(reflect.Value) string { return "" }
216         case reflect.Map:
217                 name = "entry"
218                 opts = opts.WithTypeMode(elideType)
219                 formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
220         }
221
222         maxLen := -1
223         if opts.LimitVerbosity {
224                 if opts.DiffMode == diffIdentical {
225                         maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
226                 } else {
227                         maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
228                 }
229                 opts.VerbosityLevel--
230         }
231
232         // Handle unification.
233         switch opts.DiffMode {
234         case diffIdentical, diffRemoved, diffInserted:
235                 var list textList
236                 var deferredEllipsis bool // Add final "..." to indicate records were dropped
237                 for _, r := range recs {
238                         if len(list) == maxLen {
239                                 deferredEllipsis = true
240                                 break
241                         }
242
243                         // Elide struct fields that are zero value.
244                         if k == reflect.Struct {
245                                 var isZero bool
246                                 switch opts.DiffMode {
247                                 case diffIdentical:
248                                         isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY)
249                                 case diffRemoved:
250                                         isZero = value.IsZero(r.Value.ValueX)
251                                 case diffInserted:
252                                         isZero = value.IsZero(r.Value.ValueY)
253                                 }
254                                 if isZero {
255                                         continue
256                                 }
257                         }
258                         // Elide ignored nodes.
259                         if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
260                                 deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
261                                 if !deferredEllipsis {
262                                         list.AppendEllipsis(diffStats{})
263                                 }
264                                 continue
265                         }
266                         if out := opts.FormatDiff(r.Value, ptrs); out != nil {
267                                 list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
268                         }
269                 }
270                 if deferredEllipsis {
271                         list.AppendEllipsis(diffStats{})
272                 }
273                 return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
274         case diffUnknown:
275         default:
276                 panic("invalid diff mode")
277         }
278
279         // Handle differencing.
280         var numDiffs int
281         var list textList
282         var keys []reflect.Value // invariant: len(list) == len(keys)
283         groups := coalesceAdjacentRecords(name, recs)
284         maxGroup := diffStats{Name: name}
285         for i, ds := range groups {
286                 if maxLen >= 0 && numDiffs >= maxLen {
287                         maxGroup = maxGroup.Append(ds)
288                         continue
289                 }
290
291                 // Handle equal records.
292                 if ds.NumDiff() == 0 {
293                         // Compute the number of leading and trailing records to print.
294                         var numLo, numHi int
295                         numEqual := ds.NumIgnored + ds.NumIdentical
296                         for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
297                                 if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
298                                         break
299                                 }
300                                 numLo++
301                         }
302                         for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
303                                 if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
304                                         break
305                                 }
306                                 numHi++
307                         }
308                         if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
309                                 numHi++ // Avoid pointless coalescing of a single equal record
310                         }
311
312                         // Format the equal values.
313                         for _, r := range recs[:numLo] {
314                                 out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
315                                 list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
316                                 keys = append(keys, r.Key)
317                         }
318                         if numEqual > numLo+numHi {
319                                 ds.NumIdentical -= numLo + numHi
320                                 list.AppendEllipsis(ds)
321                                 for len(keys) < len(list) {
322                                         keys = append(keys, reflect.Value{})
323                                 }
324                         }
325                         for _, r := range recs[numEqual-numHi : numEqual] {
326                                 out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
327                                 list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
328                                 keys = append(keys, r.Key)
329                         }
330                         recs = recs[numEqual:]
331                         continue
332                 }
333
334                 // Handle unequal records.
335                 for _, r := range recs[:ds.NumDiff()] {
336                         switch {
337                         case opts.CanFormatDiffSlice(r.Value):
338                                 out := opts.FormatDiffSlice(r.Value)
339                                 list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
340                                 keys = append(keys, r.Key)
341                         case r.Value.NumChildren == r.Value.MaxDepth:
342                                 outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
343                                 outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
344                                 for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
345                                         opts2 := verbosityPreset(opts, i)
346                                         outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
347                                         outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
348                                 }
349                                 if outx != nil {
350                                         list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
351                                         keys = append(keys, r.Key)
352                                 }
353                                 if outy != nil {
354                                         list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
355                                         keys = append(keys, r.Key)
356                                 }
357                         default:
358                                 out := opts.FormatDiff(r.Value, ptrs)
359                                 list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
360                                 keys = append(keys, r.Key)
361                         }
362                 }
363                 recs = recs[ds.NumDiff():]
364                 numDiffs += ds.NumDiff()
365         }
366         if maxGroup.IsZero() {
367                 assert(len(recs) == 0)
368         } else {
369                 list.AppendEllipsis(maxGroup)
370                 for len(keys) < len(list) {
371                         keys = append(keys, reflect.Value{})
372                 }
373         }
374         assert(len(list) == len(keys))
375
376         // For maps, the default formatting logic uses fmt.Stringer which may
377         // produce ambiguous output. Avoid calling String to disambiguate.
378         if k == reflect.Map {
379                 var ambiguous bool
380                 seenKeys := map[string]reflect.Value{}
381                 for i, currKey := range keys {
382                         if currKey.IsValid() {
383                                 strKey := list[i].Key
384                                 prevKey, seen := seenKeys[strKey]
385                                 if seen && prevKey.CanInterface() && currKey.CanInterface() {
386                                         ambiguous = prevKey.Interface() != currKey.Interface()
387                                         if ambiguous {
388                                                 break
389                                         }
390                                 }
391                                 seenKeys[strKey] = currKey
392                         }
393                 }
394                 if ambiguous {
395                         for i, k := range keys {
396                                 if k.IsValid() {
397                                         list[i].Key = formatMapKey(k, true, ptrs)
398                                 }
399                         }
400                 }
401         }
402
403         return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
404 }
405
406 // coalesceAdjacentRecords coalesces the list of records into groups of
407 // adjacent equal, or unequal counts.
408 func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
409         var prevCase int // Arbitrary index into which case last occurred
410         lastStats := func(i int) *diffStats {
411                 if prevCase != i {
412                         groups = append(groups, diffStats{Name: name})
413                         prevCase = i
414                 }
415                 return &groups[len(groups)-1]
416         }
417         for _, r := range recs {
418                 switch rv := r.Value; {
419                 case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
420                         lastStats(1).NumIgnored++
421                 case rv.NumDiff == 0:
422                         lastStats(1).NumIdentical++
423                 case rv.NumDiff > 0 && !rv.ValueY.IsValid():
424                         lastStats(2).NumRemoved++
425                 case rv.NumDiff > 0 && !rv.ValueX.IsValid():
426                         lastStats(2).NumInserted++
427                 default:
428                         lastStats(2).NumModified++
429                 }
430         }
431         return groups
432 }