1
2
3
4
5 package cmp
6
7 import (
8 "fmt"
9 "reflect"
10 "regexp"
11 "strings"
12
13 "github.com/google/go-cmp/cmp/internal/function"
14 )
15
16
17
18
19
20
21
22
23
24
25 type Option interface {
26
27
28
29
30
31
32 filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
33 }
34
35
36
37
38
39 type applicableOption interface {
40 Option
41
42
43 apply(s *state, vx, vy reflect.Value)
44 }
45
46
47
48
49
50 type coreOption interface {
51 Option
52 isCore()
53 }
54
55 type core struct{}
56
57 func (core) isCore() {}
58
59
60
61
62
63
64
65
66 type Options []Option
67
68 func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
69 for _, opt := range opts {
70 switch opt := opt.filter(s, t, vx, vy); opt.(type) {
71 case ignore:
72 return ignore{}
73 case validator:
74 out = validator{}
75 case *comparer, *transformer, Options:
76 switch out.(type) {
77 case nil:
78 out = opt
79 case validator:
80
81 case *comparer, *transformer, Options:
82 out = Options{out, opt}
83 }
84 }
85 }
86 return out
87 }
88
89 func (opts Options) apply(s *state, _, _ reflect.Value) {
90 const warning = "ambiguous set of applicable options"
91 const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
92 var ss []string
93 for _, opt := range flattenOptions(nil, opts) {
94 ss = append(ss, fmt.Sprint(opt))
95 }
96 set := strings.Join(ss, "\n\t")
97 panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
98 }
99
100 func (opts Options) String() string {
101 var ss []string
102 for _, opt := range opts {
103 ss = append(ss, fmt.Sprint(opt))
104 }
105 return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
106 }
107
108
109
110
111
112
113
114
115
116
117
118 func FilterPath(f func(Path) bool, opt Option) Option {
119 if f == nil {
120 panic("invalid path filter function")
121 }
122 if opt := normalizeOption(opt); opt != nil {
123 return &pathFilter{fnc: f, opt: opt}
124 }
125 return nil
126 }
127
128 type pathFilter struct {
129 core
130 fnc func(Path) bool
131 opt Option
132 }
133
134 func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
135 if f.fnc(s.curPath) {
136 return f.opt.filter(s, t, vx, vy)
137 }
138 return nil
139 }
140
141 func (f pathFilter) String() string {
142 return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 func FilterValues(f interface{}, opt Option) Option {
160 v := reflect.ValueOf(f)
161 if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
162 panic(fmt.Sprintf("invalid values filter function: %T", f))
163 }
164 if opt := normalizeOption(opt); opt != nil {
165 vf := &valuesFilter{fnc: v, opt: opt}
166 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
167 vf.typ = ti
168 }
169 return vf
170 }
171 return nil
172 }
173
174 type valuesFilter struct {
175 core
176 typ reflect.Type
177 fnc reflect.Value
178 opt Option
179 }
180
181 func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
182 if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
183 return nil
184 }
185 if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
186 return f.opt.filter(s, t, vx, vy)
187 }
188 return nil
189 }
190
191 func (f valuesFilter) String() string {
192 return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
193 }
194
195
196
197
198 func Ignore() Option { return ignore{} }
199
200 type ignore struct{ core }
201
202 func (ignore) isFiltered() bool { return false }
203 func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
204 func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
205 func (ignore) String() string { return "Ignore()" }
206
207
208
209
210 type validator struct{ core }
211
212 func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
213 if !vx.IsValid() || !vy.IsValid() {
214 return validator{}
215 }
216 if !vx.CanInterface() || !vy.CanInterface() {
217 return validator{}
218 }
219 return nil
220 }
221 func (validator) apply(s *state, vx, vy reflect.Value) {
222
223 if !vx.IsValid() || !vy.IsValid() {
224 s.report(vx.IsValid() == vy.IsValid(), 0)
225 return
226 }
227
228
229 if !vx.CanInterface() || !vy.CanInterface() {
230 help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
231 var name string
232 if t := s.curPath.Index(-2).Type(); t.Name() != "" {
233
234 name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name())
235 if _, ok := reflect.New(t).Interface().(error); ok {
236 help = "consider using cmpopts.EquateErrors to compare error values"
237 } else if t.Comparable() {
238 help = "consider using cmpopts.EquateComparable to compare comparable Go types"
239 }
240 } else {
241
242 var pkgPath string
243 for i := 0; i < t.NumField() && pkgPath == ""; i++ {
244 pkgPath = t.Field(i).PkgPath
245 }
246 name = fmt.Sprintf("%q.(%v)", pkgPath, t.String())
247 }
248 panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))
249 }
250
251 panic("not reachable")
252 }
253
254
255 const identRx = `[_\p{L}][_\p{L}\p{N}]*`
256
257 var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280 func Transformer(name string, f interface{}) Option {
281 v := reflect.ValueOf(f)
282 if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
283 panic(fmt.Sprintf("invalid transformer function: %T", f))
284 }
285 if name == "" {
286 name = function.NameOf(v)
287 if !identsRx.MatchString(name) {
288 name = "λ"
289 }
290 } else if !identsRx.MatchString(name) {
291 panic(fmt.Sprintf("invalid name: %q", name))
292 }
293 tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
294 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
295 tr.typ = ti
296 }
297 return tr
298 }
299
300 type transformer struct {
301 core
302 name string
303 typ reflect.Type
304 fnc reflect.Value
305 }
306
307 func (tr *transformer) isFiltered() bool { return tr.typ != nil }
308
309 func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
310 for i := len(s.curPath) - 1; i >= 0; i-- {
311 if t, ok := s.curPath[i].(Transform); !ok {
312 break
313 } else if tr == t.trans {
314 return nil
315 }
316 }
317 if tr.typ == nil || t.AssignableTo(tr.typ) {
318 return tr
319 }
320 return nil
321 }
322
323 func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
324 step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}
325 vvx := s.callTRFunc(tr.fnc, vx, step)
326 vvy := s.callTRFunc(tr.fnc, vy, step)
327 step.vx, step.vy = vvx, vvy
328 s.compareAny(step)
329 }
330
331 func (tr transformer) String() string {
332 return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347 func Comparer(f interface{}) Option {
348 v := reflect.ValueOf(f)
349 if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
350 panic(fmt.Sprintf("invalid comparer function: %T", f))
351 }
352 cm := &comparer{fnc: v}
353 if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
354 cm.typ = ti
355 }
356 return cm
357 }
358
359 type comparer struct {
360 core
361 typ reflect.Type
362 fnc reflect.Value
363 }
364
365 func (cm *comparer) isFiltered() bool { return cm.typ != nil }
366
367 func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
368 if cm.typ == nil || t.AssignableTo(cm.typ) {
369 return cm
370 }
371 return nil
372 }
373
374 func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
375 eq := s.callTTBFunc(cm.fnc, vx, vy)
376 s.report(eq, reportByFunc)
377 }
378
379 func (cm comparer) String() string {
380 return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 func Exporter(f func(reflect.Type) bool) Option {
409 return exporter(f)
410 }
411
412 type exporter func(reflect.Type) bool
413
414 func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
415 panic("not implemented")
416 }
417
418
419
420
421
422 func AllowUnexported(types ...interface{}) Option {
423 m := make(map[reflect.Type]bool)
424 for _, typ := range types {
425 t := reflect.TypeOf(typ)
426 if t.Kind() != reflect.Struct {
427 panic(fmt.Sprintf("invalid struct type: %T", typ))
428 }
429 m[t] = true
430 }
431 return exporter(func(t reflect.Type) bool { return m[t] })
432 }
433
434
435
436 type Result struct {
437 _ [0]func()
438 flags resultFlags
439 }
440
441
442
443 func (r Result) Equal() bool {
444 return r.flags&(reportEqual|reportByIgnore) != 0
445 }
446
447
448
449 func (r Result) ByIgnore() bool {
450 return r.flags&reportByIgnore != 0
451 }
452
453
454 func (r Result) ByMethod() bool {
455 return r.flags&reportByMethod != 0
456 }
457
458
459 func (r Result) ByFunc() bool {
460 return r.flags&reportByFunc != 0
461 }
462
463
464 func (r Result) ByCycle() bool {
465 return r.flags&reportByCycle != 0
466 }
467
468 type resultFlags uint
469
470 const (
471 _ resultFlags = (1 << iota) / 2
472
473 reportEqual
474 reportUnequal
475 reportByIgnore
476 reportByMethod
477 reportByFunc
478 reportByCycle
479 )
480
481
482
483
484
485
486 func Reporter(r interface {
487
488
489
490
491
492
493
494
495
496
497
498 PushStep(PathStep)
499
500
501
502
503
504 Report(Result)
505
506
507
508 PopStep()
509 }) Option {
510 return reporter{r}
511 }
512
513 type reporter struct{ reporterIface }
514 type reporterIface interface {
515 PushStep(PathStep)
516 Report(Result)
517 PopStep()
518 }
519
520 func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
521 panic("not implemented")
522 }
523
524
525
526
527 func normalizeOption(src Option) Option {
528 switch opts := flattenOptions(nil, Options{src}); len(opts) {
529 case 0:
530 return nil
531 case 1:
532 return opts[0]
533 default:
534 return opts
535 }
536 }
537
538
539
540 func flattenOptions(dst, src Options) Options {
541 for _, opt := range src {
542 switch opt := opt.(type) {
543 case nil:
544 continue
545 case Options:
546 dst = flattenOptions(dst, opt)
547 case coreOption:
548 dst = append(dst, opt)
549 default:
550 panic(fmt.Sprintf("invalid option type: %T", opt))
551 }
552 }
553 return dst
554 }
555
View as plain text