...

Source file src/github.com/google/go-cmp/cmp/options.go

Documentation: github.com/google/go-cmp/cmp

     1  // Copyright 2017, 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 file.
     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  // Option configures for specific behavior of [Equal] and [Diff]. In particular,
    17  // the fundamental Option functions ([Ignore], [Transformer], and [Comparer]),
    18  // configure how equality is determined.
    19  //
    20  // The fundamental options may be composed with filters ([FilterPath] and
    21  // [FilterValues]) to control the scope over which they are applied.
    22  //
    23  // The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions
    24  // for creating options that may be used with [Equal] and [Diff].
    25  type Option interface {
    26  	// filter applies all filters and returns the option that remains.
    27  	// Each option may only read s.curPath and call s.callTTBFunc.
    28  	//
    29  	// An Options is returned only if multiple comparers or transformers
    30  	// can apply simultaneously and will only contain values of those types
    31  	// or sub-Options containing values of those types.
    32  	filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
    33  }
    34  
    35  // applicableOption represents the following types:
    36  //
    37  //	Fundamental: ignore | validator | *comparer | *transformer
    38  //	Grouping:    Options
    39  type applicableOption interface {
    40  	Option
    41  
    42  	// apply executes the option, which may mutate s or panic.
    43  	apply(s *state, vx, vy reflect.Value)
    44  }
    45  
    46  // coreOption represents the following types:
    47  //
    48  //	Fundamental: ignore | validator | *comparer | *transformer
    49  //	Filters:     *pathFilter | *valuesFilter
    50  type coreOption interface {
    51  	Option
    52  	isCore()
    53  }
    54  
    55  type core struct{}
    56  
    57  func (core) isCore() {}
    58  
    59  // Options is a list of [Option] values that also satisfies the [Option] interface.
    60  // Helper comparison packages may return an Options value when packing multiple
    61  // [Option] values into a single [Option]. When this package processes an Options,
    62  // it will be implicitly expanded into a flat list.
    63  //
    64  // Applying a filter on an Options is equivalent to applying that same filter
    65  // on all individual options held within.
    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{} // Only ignore can short-circuit evaluation
    73  		case validator:
    74  			out = validator{} // Takes precedence over comparer or transformer
    75  		case *comparer, *transformer, Options:
    76  			switch out.(type) {
    77  			case nil:
    78  				out = opt
    79  			case validator:
    80  				// Keep validator
    81  			case *comparer, *transformer, Options:
    82  				out = Options{out, opt} // Conflicting comparers or transformers
    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  // FilterPath returns a new [Option] where opt is only evaluated if filter f
   109  // returns true for the current [Path] in the value tree.
   110  //
   111  // This filter is called even if a slice element or map entry is missing and
   112  // provides an opportunity to ignore such cases. The filter function must be
   113  // symmetric such that the filter result is identical regardless of whether the
   114  // missing value is from x or y.
   115  //
   116  // The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
   117  // a previously filtered [Option].
   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  // FilterValues returns a new [Option] where opt is only evaluated if filter f,
   146  // which is a function of the form "func(T, T) bool", returns true for the
   147  // current pair of values being compared. If either value is invalid or
   148  // the type of the values is not assignable to T, then this filter implicitly
   149  // returns false.
   150  //
   151  // The filter function must be
   152  // symmetric (i.e., agnostic to the order of the inputs) and
   153  // deterministic (i.e., produces the same result when given the same inputs).
   154  // If T is an interface, it is possible that f is called with two values with
   155  // different concrete types that both implement T.
   156  //
   157  // The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
   158  // a previously filtered [Option].
   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  // T
   177  	fnc reflect.Value // func(T, T) bool
   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  // Ignore is an [Option] that causes all comparisons to be ignored.
   196  // This value is intended to be combined with [FilterPath] or [FilterValues].
   197  // It is an error to pass an unfiltered Ignore option to [Equal].
   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  // validator is a sentinel Option type to indicate that some options could not
   208  // be evaluated due to unexported fields, missing slice elements, or
   209  // missing map entries. Both values are validator only for unexported fields.
   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  	// Implies missing slice element or map entry.
   223  	if !vx.IsValid() || !vy.IsValid() {
   224  		s.report(vx.IsValid() == vy.IsValid(), 0)
   225  		return
   226  	}
   227  
   228  	// Unable to Interface implies unexported field without visibility access.
   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  			// Named type with unexported fields.
   234  			name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
   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  			// Unnamed type with unexported fields. Derive PkgPath from field.
   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()) // e.g., "path/to/package".(struct { a int })
   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  // identRx represents a valid identifier according to the Go specification.
   255  const identRx = `[_\p{L}][_\p{L}\p{N}]*`
   256  
   257  var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
   258  
   259  // Transformer returns an [Option] that applies a transformation function that
   260  // converts values of a certain type into that of another.
   261  //
   262  // The transformer f must be a function "func(T) R" that converts values of
   263  // type T to those of type R and is implicitly filtered to input values
   264  // assignable to T. The transformer must not mutate T in any way.
   265  //
   266  // To help prevent some cases of infinite recursive cycles applying the
   267  // same transform to the output of itself (e.g., in the case where the
   268  // input and output types are the same), an implicit filter is added such that
   269  // a transformer is applicable only if that exact transformer is not already
   270  // in the tail of the [Path] since the last non-[Transform] step.
   271  // For situations where the implicit filter is still insufficient,
   272  // consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],
   273  // which adds a filter to prevent the transformer from
   274  // being recursively applied upon itself.
   275  //
   276  // The name is a user provided label that is used as the [Transform.Name] in the
   277  // transformation [PathStep] (and eventually shown in the [Diff] output).
   278  // The name must be a valid identifier or qualified identifier in Go syntax.
   279  // If empty, an arbitrary name is used.
   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 = "λ" // Lambda-symbol as placeholder 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  // T
   304  	fnc  reflect.Value // func(T) R
   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 // Hit most recent non-Transform step
   313  		} else if tr == t.trans {
   314  			return nil // Cannot directly use same Transform
   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  // Comparer returns an [Option] that determines whether two values are equal
   336  // to each other.
   337  //
   338  // The comparer f must be a function "func(T, T) bool" and is implicitly
   339  // filtered to input values assignable to T. If T is an interface, it is
   340  // possible that f is called with two values of different concrete types that
   341  // both implement T.
   342  //
   343  // The equality function must be:
   344  //   - Symmetric: equal(x, y) == equal(y, x)
   345  //   - Deterministic: equal(x, y) == equal(x, y)
   346  //   - Pure: equal(x, y) does not modify x or y
   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  // T
   362  	fnc reflect.Value // func(T, T) bool
   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  // Exporter returns an [Option] that specifies whether [Equal] is allowed to
   384  // introspect into the unexported fields of certain struct types.
   385  //
   386  // Users of this option must understand that comparing on unexported fields
   387  // from external packages is not safe since changes in the internal
   388  // implementation of some external package may cause the result of [Equal]
   389  // to unexpectedly change. However, it may be valid to use this option on types
   390  // defined in an internal package where the semantic meaning of an unexported
   391  // field is in the control of the user.
   392  //
   393  // In many cases, a custom [Comparer] should be used instead that defines
   394  // equality as a function of the public API of a type rather than the underlying
   395  // unexported implementation.
   396  //
   397  // For example, the [reflect.Type] documentation defines equality to be determined
   398  // by the == operator on the interface (essentially performing a shallow pointer
   399  // comparison) and most attempts to compare *[regexp.Regexp] types are interested
   400  // in only checking that the regular expression strings are equal.
   401  // Both of these are accomplished using [Comparer] options:
   402  //
   403  //	Comparer(func(x, y reflect.Type) bool { return x == y })
   404  //	Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
   405  //
   406  // In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]
   407  // option can be used to ignore all unexported fields on specified struct types.
   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  // AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect
   419  // unexported fields of the specified struct types.
   420  //
   421  // See [Exporter] for the proper use of this option.
   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  // Result represents the comparison result for a single node and
   435  // is provided by cmp when calling Report (see [Reporter]).
   436  type Result struct {
   437  	_     [0]func() // Make Result incomparable
   438  	flags resultFlags
   439  }
   440  
   441  // Equal reports whether the node was determined to be equal or not.
   442  // As a special case, ignored nodes are considered equal.
   443  func (r Result) Equal() bool {
   444  	return r.flags&(reportEqual|reportByIgnore) != 0
   445  }
   446  
   447  // ByIgnore reports whether the node is equal because it was ignored.
   448  // This never reports true if [Result.Equal] reports false.
   449  func (r Result) ByIgnore() bool {
   450  	return r.flags&reportByIgnore != 0
   451  }
   452  
   453  // ByMethod reports whether the Equal method determined equality.
   454  func (r Result) ByMethod() bool {
   455  	return r.flags&reportByMethod != 0
   456  }
   457  
   458  // ByFunc reports whether a [Comparer] function determined equality.
   459  func (r Result) ByFunc() bool {
   460  	return r.flags&reportByFunc != 0
   461  }
   462  
   463  // ByCycle reports whether a reference cycle was detected.
   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  // Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses
   482  // the value trees, it calls PushStep as it descends into each node in the
   483  // tree and PopStep as it ascend out of the node. The leaves of the tree are
   484  // either compared (determined to be equal or not equal) or ignored and reported
   485  // as such by calling the Report method.
   486  func Reporter(r interface {
   487  	// PushStep is called when a tree-traversal operation is performed.
   488  	// The PathStep itself is only valid until the step is popped.
   489  	// The PathStep.Values are valid for the duration of the entire traversal
   490  	// and must not be mutated.
   491  	//
   492  	// Equal always calls PushStep at the start to provide an operation-less
   493  	// PathStep used to report the root values.
   494  	//
   495  	// Within a slice, the exact set of inserted, removed, or modified elements
   496  	// is unspecified and may change in future implementations.
   497  	// The entries of a map are iterated through in an unspecified order.
   498  	PushStep(PathStep)
   499  
   500  	// Report is called exactly once on leaf nodes to report whether the
   501  	// comparison identified the node as equal, unequal, or ignored.
   502  	// A leaf node is one that is immediately preceded by and followed by
   503  	// a pair of PushStep and PopStep calls.
   504  	Report(Result)
   505  
   506  	// PopStep ascends back up the value tree.
   507  	// There is always a matching pop call for every push call.
   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  // normalizeOption normalizes the input options such that all Options groups
   525  // are flattened and groups with a single element are reduced to that element.
   526  // Only coreOptions and Options containing coreOptions are allowed.
   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  // flattenOptions copies all options in src to dst as a flat list.
   539  // Only coreOptions and Options containing coreOptions are allowed.
   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