1*88d15eacSSasha Smundak// Copyright 2017, The Go Authors. All rights reserved. 2*88d15eacSSasha Smundak// Use of this source code is governed by a BSD-style 3*88d15eacSSasha Smundak// license that can be found in the LICENSE file. 4*88d15eacSSasha Smundak 5*88d15eacSSasha Smundakpackage cmp 6*88d15eacSSasha Smundak 7*88d15eacSSasha Smundakimport ( 8*88d15eacSSasha Smundak "fmt" 9*88d15eacSSasha Smundak "reflect" 10*88d15eacSSasha Smundak "regexp" 11*88d15eacSSasha Smundak "strings" 12*88d15eacSSasha Smundak 13*88d15eacSSasha Smundak "github.com/google/go-cmp/cmp/internal/function" 14*88d15eacSSasha Smundak) 15*88d15eacSSasha Smundak 16*88d15eacSSasha Smundak// Option configures for specific behavior of Equal and Diff. In particular, 17*88d15eacSSasha Smundak// the fundamental Option functions (Ignore, Transformer, and Comparer), 18*88d15eacSSasha Smundak// configure how equality is determined. 19*88d15eacSSasha Smundak// 20*88d15eacSSasha Smundak// The fundamental options may be composed with filters (FilterPath and 21*88d15eacSSasha Smundak// FilterValues) to control the scope over which they are applied. 22*88d15eacSSasha Smundak// 23*88d15eacSSasha Smundak// The cmp/cmpopts package provides helper functions for creating options that 24*88d15eacSSasha Smundak// may be used with Equal and Diff. 25*88d15eacSSasha Smundaktype Option interface { 26*88d15eacSSasha Smundak // filter applies all filters and returns the option that remains. 27*88d15eacSSasha Smundak // Each option may only read s.curPath and call s.callTTBFunc. 28*88d15eacSSasha Smundak // 29*88d15eacSSasha Smundak // An Options is returned only if multiple comparers or transformers 30*88d15eacSSasha Smundak // can apply simultaneously and will only contain values of those types 31*88d15eacSSasha Smundak // or sub-Options containing values of those types. 32*88d15eacSSasha Smundak filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption 33*88d15eacSSasha Smundak} 34*88d15eacSSasha Smundak 35*88d15eacSSasha Smundak// applicableOption represents the following types: 36*88d15eacSSasha Smundak// 37*88d15eacSSasha Smundak// Fundamental: ignore | validator | *comparer | *transformer 38*88d15eacSSasha Smundak// Grouping: Options 39*88d15eacSSasha Smundaktype applicableOption interface { 40*88d15eacSSasha Smundak Option 41*88d15eacSSasha Smundak 42*88d15eacSSasha Smundak // apply executes the option, which may mutate s or panic. 43*88d15eacSSasha Smundak apply(s *state, vx, vy reflect.Value) 44*88d15eacSSasha Smundak} 45*88d15eacSSasha Smundak 46*88d15eacSSasha Smundak// coreOption represents the following types: 47*88d15eacSSasha Smundak// 48*88d15eacSSasha Smundak// Fundamental: ignore | validator | *comparer | *transformer 49*88d15eacSSasha Smundak// Filters: *pathFilter | *valuesFilter 50*88d15eacSSasha Smundaktype coreOption interface { 51*88d15eacSSasha Smundak Option 52*88d15eacSSasha Smundak isCore() 53*88d15eacSSasha Smundak} 54*88d15eacSSasha Smundak 55*88d15eacSSasha Smundaktype core struct{} 56*88d15eacSSasha Smundak 57*88d15eacSSasha Smundakfunc (core) isCore() {} 58*88d15eacSSasha Smundak 59*88d15eacSSasha Smundak// Options is a list of Option values that also satisfies the Option interface. 60*88d15eacSSasha Smundak// Helper comparison packages may return an Options value when packing multiple 61*88d15eacSSasha Smundak// Option values into a single Option. When this package processes an Options, 62*88d15eacSSasha Smundak// it will be implicitly expanded into a flat list. 63*88d15eacSSasha Smundak// 64*88d15eacSSasha Smundak// Applying a filter on an Options is equivalent to applying that same filter 65*88d15eacSSasha Smundak// on all individual options held within. 66*88d15eacSSasha Smundaktype Options []Option 67*88d15eacSSasha Smundak 68*88d15eacSSasha Smundakfunc (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) { 69*88d15eacSSasha Smundak for _, opt := range opts { 70*88d15eacSSasha Smundak switch opt := opt.filter(s, t, vx, vy); opt.(type) { 71*88d15eacSSasha Smundak case ignore: 72*88d15eacSSasha Smundak return ignore{} // Only ignore can short-circuit evaluation 73*88d15eacSSasha Smundak case validator: 74*88d15eacSSasha Smundak out = validator{} // Takes precedence over comparer or transformer 75*88d15eacSSasha Smundak case *comparer, *transformer, Options: 76*88d15eacSSasha Smundak switch out.(type) { 77*88d15eacSSasha Smundak case nil: 78*88d15eacSSasha Smundak out = opt 79*88d15eacSSasha Smundak case validator: 80*88d15eacSSasha Smundak // Keep validator 81*88d15eacSSasha Smundak case *comparer, *transformer, Options: 82*88d15eacSSasha Smundak out = Options{out, opt} // Conflicting comparers or transformers 83*88d15eacSSasha Smundak } 84*88d15eacSSasha Smundak } 85*88d15eacSSasha Smundak } 86*88d15eacSSasha Smundak return out 87*88d15eacSSasha Smundak} 88*88d15eacSSasha Smundak 89*88d15eacSSasha Smundakfunc (opts Options) apply(s *state, _, _ reflect.Value) { 90*88d15eacSSasha Smundak const warning = "ambiguous set of applicable options" 91*88d15eacSSasha Smundak const help = "consider using filters to ensure at most one Comparer or Transformer may apply" 92*88d15eacSSasha Smundak var ss []string 93*88d15eacSSasha Smundak for _, opt := range flattenOptions(nil, opts) { 94*88d15eacSSasha Smundak ss = append(ss, fmt.Sprint(opt)) 95*88d15eacSSasha Smundak } 96*88d15eacSSasha Smundak set := strings.Join(ss, "\n\t") 97*88d15eacSSasha Smundak panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help)) 98*88d15eacSSasha Smundak} 99*88d15eacSSasha Smundak 100*88d15eacSSasha Smundakfunc (opts Options) String() string { 101*88d15eacSSasha Smundak var ss []string 102*88d15eacSSasha Smundak for _, opt := range opts { 103*88d15eacSSasha Smundak ss = append(ss, fmt.Sprint(opt)) 104*88d15eacSSasha Smundak } 105*88d15eacSSasha Smundak return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) 106*88d15eacSSasha Smundak} 107*88d15eacSSasha Smundak 108*88d15eacSSasha Smundak// FilterPath returns a new Option where opt is only evaluated if filter f 109*88d15eacSSasha Smundak// returns true for the current Path in the value tree. 110*88d15eacSSasha Smundak// 111*88d15eacSSasha Smundak// This filter is called even if a slice element or map entry is missing and 112*88d15eacSSasha Smundak// provides an opportunity to ignore such cases. The filter function must be 113*88d15eacSSasha Smundak// symmetric such that the filter result is identical regardless of whether the 114*88d15eacSSasha Smundak// missing value is from x or y. 115*88d15eacSSasha Smundak// 116*88d15eacSSasha Smundak// The option passed in may be an Ignore, Transformer, Comparer, Options, or 117*88d15eacSSasha Smundak// a previously filtered Option. 118*88d15eacSSasha Smundakfunc FilterPath(f func(Path) bool, opt Option) Option { 119*88d15eacSSasha Smundak if f == nil { 120*88d15eacSSasha Smundak panic("invalid path filter function") 121*88d15eacSSasha Smundak } 122*88d15eacSSasha Smundak if opt := normalizeOption(opt); opt != nil { 123*88d15eacSSasha Smundak return &pathFilter{fnc: f, opt: opt} 124*88d15eacSSasha Smundak } 125*88d15eacSSasha Smundak return nil 126*88d15eacSSasha Smundak} 127*88d15eacSSasha Smundak 128*88d15eacSSasha Smundaktype pathFilter struct { 129*88d15eacSSasha Smundak core 130*88d15eacSSasha Smundak fnc func(Path) bool 131*88d15eacSSasha Smundak opt Option 132*88d15eacSSasha Smundak} 133*88d15eacSSasha Smundak 134*88d15eacSSasha Smundakfunc (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { 135*88d15eacSSasha Smundak if f.fnc(s.curPath) { 136*88d15eacSSasha Smundak return f.opt.filter(s, t, vx, vy) 137*88d15eacSSasha Smundak } 138*88d15eacSSasha Smundak return nil 139*88d15eacSSasha Smundak} 140*88d15eacSSasha Smundak 141*88d15eacSSasha Smundakfunc (f pathFilter) String() string { 142*88d15eacSSasha Smundak return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) 143*88d15eacSSasha Smundak} 144*88d15eacSSasha Smundak 145*88d15eacSSasha Smundak// FilterValues returns a new Option where opt is only evaluated if filter f, 146*88d15eacSSasha Smundak// which is a function of the form "func(T, T) bool", returns true for the 147*88d15eacSSasha Smundak// current pair of values being compared. If either value is invalid or 148*88d15eacSSasha Smundak// the type of the values is not assignable to T, then this filter implicitly 149*88d15eacSSasha Smundak// returns false. 150*88d15eacSSasha Smundak// 151*88d15eacSSasha Smundak// The filter function must be 152*88d15eacSSasha Smundak// symmetric (i.e., agnostic to the order of the inputs) and 153*88d15eacSSasha Smundak// deterministic (i.e., produces the same result when given the same inputs). 154*88d15eacSSasha Smundak// If T is an interface, it is possible that f is called with two values with 155*88d15eacSSasha Smundak// different concrete types that both implement T. 156*88d15eacSSasha Smundak// 157*88d15eacSSasha Smundak// The option passed in may be an Ignore, Transformer, Comparer, Options, or 158*88d15eacSSasha Smundak// a previously filtered Option. 159*88d15eacSSasha Smundakfunc FilterValues(f interface{}, opt Option) Option { 160*88d15eacSSasha Smundak v := reflect.ValueOf(f) 161*88d15eacSSasha Smundak if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { 162*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid values filter function: %T", f)) 163*88d15eacSSasha Smundak } 164*88d15eacSSasha Smundak if opt := normalizeOption(opt); opt != nil { 165*88d15eacSSasha Smundak vf := &valuesFilter{fnc: v, opt: opt} 166*88d15eacSSasha Smundak if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 167*88d15eacSSasha Smundak vf.typ = ti 168*88d15eacSSasha Smundak } 169*88d15eacSSasha Smundak return vf 170*88d15eacSSasha Smundak } 171*88d15eacSSasha Smundak return nil 172*88d15eacSSasha Smundak} 173*88d15eacSSasha Smundak 174*88d15eacSSasha Smundaktype valuesFilter struct { 175*88d15eacSSasha Smundak core 176*88d15eacSSasha Smundak typ reflect.Type // T 177*88d15eacSSasha Smundak fnc reflect.Value // func(T, T) bool 178*88d15eacSSasha Smundak opt Option 179*88d15eacSSasha Smundak} 180*88d15eacSSasha Smundak 181*88d15eacSSasha Smundakfunc (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { 182*88d15eacSSasha Smundak if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() { 183*88d15eacSSasha Smundak return nil 184*88d15eacSSasha Smundak } 185*88d15eacSSasha Smundak if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { 186*88d15eacSSasha Smundak return f.opt.filter(s, t, vx, vy) 187*88d15eacSSasha Smundak } 188*88d15eacSSasha Smundak return nil 189*88d15eacSSasha Smundak} 190*88d15eacSSasha Smundak 191*88d15eacSSasha Smundakfunc (f valuesFilter) String() string { 192*88d15eacSSasha Smundak return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) 193*88d15eacSSasha Smundak} 194*88d15eacSSasha Smundak 195*88d15eacSSasha Smundak// Ignore is an Option that causes all comparisons to be ignored. 196*88d15eacSSasha Smundak// This value is intended to be combined with FilterPath or FilterValues. 197*88d15eacSSasha Smundak// It is an error to pass an unfiltered Ignore option to Equal. 198*88d15eacSSasha Smundakfunc Ignore() Option { return ignore{} } 199*88d15eacSSasha Smundak 200*88d15eacSSasha Smundaktype ignore struct{ core } 201*88d15eacSSasha Smundak 202*88d15eacSSasha Smundakfunc (ignore) isFiltered() bool { return false } 203*88d15eacSSasha Smundakfunc (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} } 204*88d15eacSSasha Smundakfunc (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) } 205*88d15eacSSasha Smundakfunc (ignore) String() string { return "Ignore()" } 206*88d15eacSSasha Smundak 207*88d15eacSSasha Smundak// validator is a sentinel Option type to indicate that some options could not 208*88d15eacSSasha Smundak// be evaluated due to unexported fields, missing slice elements, or 209*88d15eacSSasha Smundak// missing map entries. Both values are validator only for unexported fields. 210*88d15eacSSasha Smundaktype validator struct{ core } 211*88d15eacSSasha Smundak 212*88d15eacSSasha Smundakfunc (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption { 213*88d15eacSSasha Smundak if !vx.IsValid() || !vy.IsValid() { 214*88d15eacSSasha Smundak return validator{} 215*88d15eacSSasha Smundak } 216*88d15eacSSasha Smundak if !vx.CanInterface() || !vy.CanInterface() { 217*88d15eacSSasha Smundak return validator{} 218*88d15eacSSasha Smundak } 219*88d15eacSSasha Smundak return nil 220*88d15eacSSasha Smundak} 221*88d15eacSSasha Smundakfunc (validator) apply(s *state, vx, vy reflect.Value) { 222*88d15eacSSasha Smundak // Implies missing slice element or map entry. 223*88d15eacSSasha Smundak if !vx.IsValid() || !vy.IsValid() { 224*88d15eacSSasha Smundak s.report(vx.IsValid() == vy.IsValid(), 0) 225*88d15eacSSasha Smundak return 226*88d15eacSSasha Smundak } 227*88d15eacSSasha Smundak 228*88d15eacSSasha Smundak // Unable to Interface implies unexported field without visibility access. 229*88d15eacSSasha Smundak if !vx.CanInterface() || !vy.CanInterface() { 230*88d15eacSSasha Smundak 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*88d15eacSSasha Smundak var name string 232*88d15eacSSasha Smundak if t := s.curPath.Index(-2).Type(); t.Name() != "" { 233*88d15eacSSasha Smundak // Named type with unexported fields. 234*88d15eacSSasha Smundak name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType 235*88d15eacSSasha Smundak if _, ok := reflect.New(t).Interface().(error); ok { 236*88d15eacSSasha Smundak help = "consider using cmpopts.EquateErrors to compare error values" 237*88d15eacSSasha Smundak } 238*88d15eacSSasha Smundak } else { 239*88d15eacSSasha Smundak // Unnamed type with unexported fields. Derive PkgPath from field. 240*88d15eacSSasha Smundak var pkgPath string 241*88d15eacSSasha Smundak for i := 0; i < t.NumField() && pkgPath == ""; i++ { 242*88d15eacSSasha Smundak pkgPath = t.Field(i).PkgPath 243*88d15eacSSasha Smundak } 244*88d15eacSSasha Smundak name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int }) 245*88d15eacSSasha Smundak } 246*88d15eacSSasha Smundak panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help)) 247*88d15eacSSasha Smundak } 248*88d15eacSSasha Smundak 249*88d15eacSSasha Smundak panic("not reachable") 250*88d15eacSSasha Smundak} 251*88d15eacSSasha Smundak 252*88d15eacSSasha Smundak// identRx represents a valid identifier according to the Go specification. 253*88d15eacSSasha Smundakconst identRx = `[_\p{L}][_\p{L}\p{N}]*` 254*88d15eacSSasha Smundak 255*88d15eacSSasha Smundakvar identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) 256*88d15eacSSasha Smundak 257*88d15eacSSasha Smundak// Transformer returns an Option that applies a transformation function that 258*88d15eacSSasha Smundak// converts values of a certain type into that of another. 259*88d15eacSSasha Smundak// 260*88d15eacSSasha Smundak// The transformer f must be a function "func(T) R" that converts values of 261*88d15eacSSasha Smundak// type T to those of type R and is implicitly filtered to input values 262*88d15eacSSasha Smundak// assignable to T. The transformer must not mutate T in any way. 263*88d15eacSSasha Smundak// 264*88d15eacSSasha Smundak// To help prevent some cases of infinite recursive cycles applying the 265*88d15eacSSasha Smundak// same transform to the output of itself (e.g., in the case where the 266*88d15eacSSasha Smundak// input and output types are the same), an implicit filter is added such that 267*88d15eacSSasha Smundak// a transformer is applicable only if that exact transformer is not already 268*88d15eacSSasha Smundak// in the tail of the Path since the last non-Transform step. 269*88d15eacSSasha Smundak// For situations where the implicit filter is still insufficient, 270*88d15eacSSasha Smundak// consider using cmpopts.AcyclicTransformer, which adds a filter 271*88d15eacSSasha Smundak// to prevent the transformer from being recursively applied upon itself. 272*88d15eacSSasha Smundak// 273*88d15eacSSasha Smundak// The name is a user provided label that is used as the Transform.Name in the 274*88d15eacSSasha Smundak// transformation PathStep (and eventually shown in the Diff output). 275*88d15eacSSasha Smundak// The name must be a valid identifier or qualified identifier in Go syntax. 276*88d15eacSSasha Smundak// If empty, an arbitrary name is used. 277*88d15eacSSasha Smundakfunc Transformer(name string, f interface{}) Option { 278*88d15eacSSasha Smundak v := reflect.ValueOf(f) 279*88d15eacSSasha Smundak if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { 280*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid transformer function: %T", f)) 281*88d15eacSSasha Smundak } 282*88d15eacSSasha Smundak if name == "" { 283*88d15eacSSasha Smundak name = function.NameOf(v) 284*88d15eacSSasha Smundak if !identsRx.MatchString(name) { 285*88d15eacSSasha Smundak name = "λ" // Lambda-symbol as placeholder name 286*88d15eacSSasha Smundak } 287*88d15eacSSasha Smundak } else if !identsRx.MatchString(name) { 288*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid name: %q", name)) 289*88d15eacSSasha Smundak } 290*88d15eacSSasha Smundak tr := &transformer{name: name, fnc: reflect.ValueOf(f)} 291*88d15eacSSasha Smundak if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 292*88d15eacSSasha Smundak tr.typ = ti 293*88d15eacSSasha Smundak } 294*88d15eacSSasha Smundak return tr 295*88d15eacSSasha Smundak} 296*88d15eacSSasha Smundak 297*88d15eacSSasha Smundaktype transformer struct { 298*88d15eacSSasha Smundak core 299*88d15eacSSasha Smundak name string 300*88d15eacSSasha Smundak typ reflect.Type // T 301*88d15eacSSasha Smundak fnc reflect.Value // func(T) R 302*88d15eacSSasha Smundak} 303*88d15eacSSasha Smundak 304*88d15eacSSasha Smundakfunc (tr *transformer) isFiltered() bool { return tr.typ != nil } 305*88d15eacSSasha Smundak 306*88d15eacSSasha Smundakfunc (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption { 307*88d15eacSSasha Smundak for i := len(s.curPath) - 1; i >= 0; i-- { 308*88d15eacSSasha Smundak if t, ok := s.curPath[i].(Transform); !ok { 309*88d15eacSSasha Smundak break // Hit most recent non-Transform step 310*88d15eacSSasha Smundak } else if tr == t.trans { 311*88d15eacSSasha Smundak return nil // Cannot directly use same Transform 312*88d15eacSSasha Smundak } 313*88d15eacSSasha Smundak } 314*88d15eacSSasha Smundak if tr.typ == nil || t.AssignableTo(tr.typ) { 315*88d15eacSSasha Smundak return tr 316*88d15eacSSasha Smundak } 317*88d15eacSSasha Smundak return nil 318*88d15eacSSasha Smundak} 319*88d15eacSSasha Smundak 320*88d15eacSSasha Smundakfunc (tr *transformer) apply(s *state, vx, vy reflect.Value) { 321*88d15eacSSasha Smundak step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}} 322*88d15eacSSasha Smundak vvx := s.callTRFunc(tr.fnc, vx, step) 323*88d15eacSSasha Smundak vvy := s.callTRFunc(tr.fnc, vy, step) 324*88d15eacSSasha Smundak step.vx, step.vy = vvx, vvy 325*88d15eacSSasha Smundak s.compareAny(step) 326*88d15eacSSasha Smundak} 327*88d15eacSSasha Smundak 328*88d15eacSSasha Smundakfunc (tr transformer) String() string { 329*88d15eacSSasha Smundak return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) 330*88d15eacSSasha Smundak} 331*88d15eacSSasha Smundak 332*88d15eacSSasha Smundak// Comparer returns an Option that determines whether two values are equal 333*88d15eacSSasha Smundak// to each other. 334*88d15eacSSasha Smundak// 335*88d15eacSSasha Smundak// The comparer f must be a function "func(T, T) bool" and is implicitly 336*88d15eacSSasha Smundak// filtered to input values assignable to T. If T is an interface, it is 337*88d15eacSSasha Smundak// possible that f is called with two values of different concrete types that 338*88d15eacSSasha Smundak// both implement T. 339*88d15eacSSasha Smundak// 340*88d15eacSSasha Smundak// The equality function must be: 341*88d15eacSSasha Smundak// - Symmetric: equal(x, y) == equal(y, x) 342*88d15eacSSasha Smundak// - Deterministic: equal(x, y) == equal(x, y) 343*88d15eacSSasha Smundak// - Pure: equal(x, y) does not modify x or y 344*88d15eacSSasha Smundakfunc Comparer(f interface{}) Option { 345*88d15eacSSasha Smundak v := reflect.ValueOf(f) 346*88d15eacSSasha Smundak if !function.IsType(v.Type(), function.Equal) || v.IsNil() { 347*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid comparer function: %T", f)) 348*88d15eacSSasha Smundak } 349*88d15eacSSasha Smundak cm := &comparer{fnc: v} 350*88d15eacSSasha Smundak if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { 351*88d15eacSSasha Smundak cm.typ = ti 352*88d15eacSSasha Smundak } 353*88d15eacSSasha Smundak return cm 354*88d15eacSSasha Smundak} 355*88d15eacSSasha Smundak 356*88d15eacSSasha Smundaktype comparer struct { 357*88d15eacSSasha Smundak core 358*88d15eacSSasha Smundak typ reflect.Type // T 359*88d15eacSSasha Smundak fnc reflect.Value // func(T, T) bool 360*88d15eacSSasha Smundak} 361*88d15eacSSasha Smundak 362*88d15eacSSasha Smundakfunc (cm *comparer) isFiltered() bool { return cm.typ != nil } 363*88d15eacSSasha Smundak 364*88d15eacSSasha Smundakfunc (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption { 365*88d15eacSSasha Smundak if cm.typ == nil || t.AssignableTo(cm.typ) { 366*88d15eacSSasha Smundak return cm 367*88d15eacSSasha Smundak } 368*88d15eacSSasha Smundak return nil 369*88d15eacSSasha Smundak} 370*88d15eacSSasha Smundak 371*88d15eacSSasha Smundakfunc (cm *comparer) apply(s *state, vx, vy reflect.Value) { 372*88d15eacSSasha Smundak eq := s.callTTBFunc(cm.fnc, vx, vy) 373*88d15eacSSasha Smundak s.report(eq, reportByFunc) 374*88d15eacSSasha Smundak} 375*88d15eacSSasha Smundak 376*88d15eacSSasha Smundakfunc (cm comparer) String() string { 377*88d15eacSSasha Smundak return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) 378*88d15eacSSasha Smundak} 379*88d15eacSSasha Smundak 380*88d15eacSSasha Smundak// Exporter returns an Option that specifies whether Equal is allowed to 381*88d15eacSSasha Smundak// introspect into the unexported fields of certain struct types. 382*88d15eacSSasha Smundak// 383*88d15eacSSasha Smundak// Users of this option must understand that comparing on unexported fields 384*88d15eacSSasha Smundak// from external packages is not safe since changes in the internal 385*88d15eacSSasha Smundak// implementation of some external package may cause the result of Equal 386*88d15eacSSasha Smundak// to unexpectedly change. However, it may be valid to use this option on types 387*88d15eacSSasha Smundak// defined in an internal package where the semantic meaning of an unexported 388*88d15eacSSasha Smundak// field is in the control of the user. 389*88d15eacSSasha Smundak// 390*88d15eacSSasha Smundak// In many cases, a custom Comparer should be used instead that defines 391*88d15eacSSasha Smundak// equality as a function of the public API of a type rather than the underlying 392*88d15eacSSasha Smundak// unexported implementation. 393*88d15eacSSasha Smundak// 394*88d15eacSSasha Smundak// For example, the reflect.Type documentation defines equality to be determined 395*88d15eacSSasha Smundak// by the == operator on the interface (essentially performing a shallow pointer 396*88d15eacSSasha Smundak// comparison) and most attempts to compare *regexp.Regexp types are interested 397*88d15eacSSasha Smundak// in only checking that the regular expression strings are equal. 398*88d15eacSSasha Smundak// Both of these are accomplished using Comparers: 399*88d15eacSSasha Smundak// 400*88d15eacSSasha Smundak// Comparer(func(x, y reflect.Type) bool { return x == y }) 401*88d15eacSSasha Smundak// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) 402*88d15eacSSasha Smundak// 403*88d15eacSSasha Smundak// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore 404*88d15eacSSasha Smundak// all unexported fields on specified struct types. 405*88d15eacSSasha Smundakfunc Exporter(f func(reflect.Type) bool) Option { 406*88d15eacSSasha Smundak if !supportExporters { 407*88d15eacSSasha Smundak panic("Exporter is not supported on purego builds") 408*88d15eacSSasha Smundak } 409*88d15eacSSasha Smundak return exporter(f) 410*88d15eacSSasha Smundak} 411*88d15eacSSasha Smundak 412*88d15eacSSasha Smundaktype exporter func(reflect.Type) bool 413*88d15eacSSasha Smundak 414*88d15eacSSasha Smundakfunc (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { 415*88d15eacSSasha Smundak panic("not implemented") 416*88d15eacSSasha Smundak} 417*88d15eacSSasha Smundak 418*88d15eacSSasha Smundak// AllowUnexported returns an Options that allows Equal to forcibly introspect 419*88d15eacSSasha Smundak// unexported fields of the specified struct types. 420*88d15eacSSasha Smundak// 421*88d15eacSSasha Smundak// See Exporter for the proper use of this option. 422*88d15eacSSasha Smundakfunc AllowUnexported(types ...interface{}) Option { 423*88d15eacSSasha Smundak m := make(map[reflect.Type]bool) 424*88d15eacSSasha Smundak for _, typ := range types { 425*88d15eacSSasha Smundak t := reflect.TypeOf(typ) 426*88d15eacSSasha Smundak if t.Kind() != reflect.Struct { 427*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid struct type: %T", typ)) 428*88d15eacSSasha Smundak } 429*88d15eacSSasha Smundak m[t] = true 430*88d15eacSSasha Smundak } 431*88d15eacSSasha Smundak return exporter(func(t reflect.Type) bool { return m[t] }) 432*88d15eacSSasha Smundak} 433*88d15eacSSasha Smundak 434*88d15eacSSasha Smundak// Result represents the comparison result for a single node and 435*88d15eacSSasha Smundak// is provided by cmp when calling Report (see Reporter). 436*88d15eacSSasha Smundaktype Result struct { 437*88d15eacSSasha Smundak _ [0]func() // Make Result incomparable 438*88d15eacSSasha Smundak flags resultFlags 439*88d15eacSSasha Smundak} 440*88d15eacSSasha Smundak 441*88d15eacSSasha Smundak// Equal reports whether the node was determined to be equal or not. 442*88d15eacSSasha Smundak// As a special case, ignored nodes are considered equal. 443*88d15eacSSasha Smundakfunc (r Result) Equal() bool { 444*88d15eacSSasha Smundak return r.flags&(reportEqual|reportByIgnore) != 0 445*88d15eacSSasha Smundak} 446*88d15eacSSasha Smundak 447*88d15eacSSasha Smundak// ByIgnore reports whether the node is equal because it was ignored. 448*88d15eacSSasha Smundak// This never reports true if Equal reports false. 449*88d15eacSSasha Smundakfunc (r Result) ByIgnore() bool { 450*88d15eacSSasha Smundak return r.flags&reportByIgnore != 0 451*88d15eacSSasha Smundak} 452*88d15eacSSasha Smundak 453*88d15eacSSasha Smundak// ByMethod reports whether the Equal method determined equality. 454*88d15eacSSasha Smundakfunc (r Result) ByMethod() bool { 455*88d15eacSSasha Smundak return r.flags&reportByMethod != 0 456*88d15eacSSasha Smundak} 457*88d15eacSSasha Smundak 458*88d15eacSSasha Smundak// ByFunc reports whether a Comparer function determined equality. 459*88d15eacSSasha Smundakfunc (r Result) ByFunc() bool { 460*88d15eacSSasha Smundak return r.flags&reportByFunc != 0 461*88d15eacSSasha Smundak} 462*88d15eacSSasha Smundak 463*88d15eacSSasha Smundak// ByCycle reports whether a reference cycle was detected. 464*88d15eacSSasha Smundakfunc (r Result) ByCycle() bool { 465*88d15eacSSasha Smundak return r.flags&reportByCycle != 0 466*88d15eacSSasha Smundak} 467*88d15eacSSasha Smundak 468*88d15eacSSasha Smundaktype resultFlags uint 469*88d15eacSSasha Smundak 470*88d15eacSSasha Smundakconst ( 471*88d15eacSSasha Smundak _ resultFlags = (1 << iota) / 2 472*88d15eacSSasha Smundak 473*88d15eacSSasha Smundak reportEqual 474*88d15eacSSasha Smundak reportUnequal 475*88d15eacSSasha Smundak reportByIgnore 476*88d15eacSSasha Smundak reportByMethod 477*88d15eacSSasha Smundak reportByFunc 478*88d15eacSSasha Smundak reportByCycle 479*88d15eacSSasha Smundak) 480*88d15eacSSasha Smundak 481*88d15eacSSasha Smundak// Reporter is an Option that can be passed to Equal. When Equal traverses 482*88d15eacSSasha Smundak// the value trees, it calls PushStep as it descends into each node in the 483*88d15eacSSasha Smundak// tree and PopStep as it ascend out of the node. The leaves of the tree are 484*88d15eacSSasha Smundak// either compared (determined to be equal or not equal) or ignored and reported 485*88d15eacSSasha Smundak// as such by calling the Report method. 486*88d15eacSSasha Smundakfunc Reporter(r interface { 487*88d15eacSSasha Smundak // PushStep is called when a tree-traversal operation is performed. 488*88d15eacSSasha Smundak // The PathStep itself is only valid until the step is popped. 489*88d15eacSSasha Smundak // The PathStep.Values are valid for the duration of the entire traversal 490*88d15eacSSasha Smundak // and must not be mutated. 491*88d15eacSSasha Smundak // 492*88d15eacSSasha Smundak // Equal always calls PushStep at the start to provide an operation-less 493*88d15eacSSasha Smundak // PathStep used to report the root values. 494*88d15eacSSasha Smundak // 495*88d15eacSSasha Smundak // Within a slice, the exact set of inserted, removed, or modified elements 496*88d15eacSSasha Smundak // is unspecified and may change in future implementations. 497*88d15eacSSasha Smundak // The entries of a map are iterated through in an unspecified order. 498*88d15eacSSasha Smundak PushStep(PathStep) 499*88d15eacSSasha Smundak 500*88d15eacSSasha Smundak // Report is called exactly once on leaf nodes to report whether the 501*88d15eacSSasha Smundak // comparison identified the node as equal, unequal, or ignored. 502*88d15eacSSasha Smundak // A leaf node is one that is immediately preceded by and followed by 503*88d15eacSSasha Smundak // a pair of PushStep and PopStep calls. 504*88d15eacSSasha Smundak Report(Result) 505*88d15eacSSasha Smundak 506*88d15eacSSasha Smundak // PopStep ascends back up the value tree. 507*88d15eacSSasha Smundak // There is always a matching pop call for every push call. 508*88d15eacSSasha Smundak PopStep() 509*88d15eacSSasha Smundak}) Option { 510*88d15eacSSasha Smundak return reporter{r} 511*88d15eacSSasha Smundak} 512*88d15eacSSasha Smundak 513*88d15eacSSasha Smundaktype reporter struct{ reporterIface } 514*88d15eacSSasha Smundaktype reporterIface interface { 515*88d15eacSSasha Smundak PushStep(PathStep) 516*88d15eacSSasha Smundak Report(Result) 517*88d15eacSSasha Smundak PopStep() 518*88d15eacSSasha Smundak} 519*88d15eacSSasha Smundak 520*88d15eacSSasha Smundakfunc (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { 521*88d15eacSSasha Smundak panic("not implemented") 522*88d15eacSSasha Smundak} 523*88d15eacSSasha Smundak 524*88d15eacSSasha Smundak// normalizeOption normalizes the input options such that all Options groups 525*88d15eacSSasha Smundak// are flattened and groups with a single element are reduced to that element. 526*88d15eacSSasha Smundak// Only coreOptions and Options containing coreOptions are allowed. 527*88d15eacSSasha Smundakfunc normalizeOption(src Option) Option { 528*88d15eacSSasha Smundak switch opts := flattenOptions(nil, Options{src}); len(opts) { 529*88d15eacSSasha Smundak case 0: 530*88d15eacSSasha Smundak return nil 531*88d15eacSSasha Smundak case 1: 532*88d15eacSSasha Smundak return opts[0] 533*88d15eacSSasha Smundak default: 534*88d15eacSSasha Smundak return opts 535*88d15eacSSasha Smundak } 536*88d15eacSSasha Smundak} 537*88d15eacSSasha Smundak 538*88d15eacSSasha Smundak// flattenOptions copies all options in src to dst as a flat list. 539*88d15eacSSasha Smundak// Only coreOptions and Options containing coreOptions are allowed. 540*88d15eacSSasha Smundakfunc flattenOptions(dst, src Options) Options { 541*88d15eacSSasha Smundak for _, opt := range src { 542*88d15eacSSasha Smundak switch opt := opt.(type) { 543*88d15eacSSasha Smundak case nil: 544*88d15eacSSasha Smundak continue 545*88d15eacSSasha Smundak case Options: 546*88d15eacSSasha Smundak dst = flattenOptions(dst, opt) 547*88d15eacSSasha Smundak case coreOption: 548*88d15eacSSasha Smundak dst = append(dst, opt) 549*88d15eacSSasha Smundak default: 550*88d15eacSSasha Smundak panic(fmt.Sprintf("invalid option type: %T", opt)) 551*88d15eacSSasha Smundak } 552*88d15eacSSasha Smundak } 553*88d15eacSSasha Smundak return dst 554*88d15eacSSasha Smundak} 555